7.6.1 Postfix expressions

7.6.1.6 Dynamic cast

1
表达式`dynamic_cast(v)`的结果,是将表达式v转换为类型T的结果。T必须是指向完整类类型的指针或引用,或者“指向cv限定的void的指针”。`dynamic_cast`运算符不得移除常量性(见[[expr.const.cast]]())。
2
- ①若T是指针类型,则v应为指向完整类类型的指针的纯右值,且结果是类型为T的纯右值。 - ②若T是左值引用类型,则v应为完整类类型的左值,且结果是T所引用类型的左值。 - ③若T是右值引用类型,则v应为具有完整类类型的泛左值,且结果是T所引用类型的将亡值。
Paw5zx个人理解:点击查看更多

待补充

3
如果v的类型与T相同(忽略cv限定符),结果是v(如果需要会进行转换)
Paw5zx个人理解:点击查看更多

类型相同则不做RTTI检查,结果就是v本身;必要时做qualification conversion,例如添加const等(待验证)

4
若T为“指向cv1 B的指针”,且v的类型为“指向cv2 D的指针”,并且B是D的基类,则结果为:v所指向之D对象中唯一的B子对象的指针;若 v 为空指针,则结果为空指针。 同理,若T为“cv1 B的引用”,且v的类型为cv2 D,并且B是D的基类,则结果为:v所引用之D对象中唯一的B子对象。 在指针与引用两种情形下,若B是D的不可访问或不唯一(有二义性)的基类,则程序为非法的(ill-formed)。【示例:
1
2
3
4
5
struct B { };
struct D : B { };
void foo(D* dp) {
B* bp = dynamic_cast<B*>(dp); // equivalent to B* bp = dp;
}
——示例结束】
Paw5zx个人理解:点击查看更多

对于向上转换,dynamic_cast等价于普通的隐式转换

5
否则,v应当是指向多态类型的指针或多态类型的泛左值
6
如果v是空指针值,则结果是空指针值
7
- 如果T是“指向cv void的指针”,则结果是v所指向的最派生对象的指针。 - 否则,将应用运行时检查,以查看v所指向或引用的对象是否可以转换为T所指向或引用的类型。
Paw5zx个人理解:点击查看更多
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct B1 { virtual ~B1() = default; };
struct B2 { virtual ~B2() = default; };
struct M : B1, B2 {};

int main()
{
M m;
std::cout << "address of M object: " << &m << std::endl;;
B1* pB1 = &m; // 指向M的B1子对象(是首地址)
std::cout << "address of B1 subobject: " << pB1 << std::endl;
B2* pB2 = &m; // 指向M的B2子对象(不是首地址)
std::cout << "address of B2 subobject: " << pB2 << std::endl;
void* pv = dynamic_cast<void*>(pB2); // → 返回M的首地址
std::cout << "address of most derived object: " << pv << std::endl;
}
8
对于`dynamic_cast(v)`,设C为T指向或引用的类类型,则运行时检查的执行逻辑如下: - (8.1) 若在v指向(或引用)的最派生对象中,v指向(或引用)C对象的一个公有基类子对象,并且该子对象派生出的C类型对象只有唯一一个,则结果指向(引用)该C对象。 - (8.2) 否则,如果 v 指向(引用)了最派生对象的一个公有基类子对象,并且最派生对象的类型有一个明确的、公有的C类型基类,则结果指向(引用)最派生对象中的C的子对象。 - (8.3) 否则,运行时检查失败。
Paw5zx个人理解(针对8.1):点击查看更多

8.1对应的是指针向下转型(downcast)

以此为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct B { virtual ~B() = default; };
struct C : public B {};
struct E {};
struct M : public C, public E {};

int main()
{
M m; // 最派生对象m
C* pc = &m; // 指向M里的C子对象
B* v = pc; // v:指向“上述C子对象的B基类子对象”

// 解释:在最派生对象m中,pb指向的是一个C对象(即pc指向的那个C)的公有基类子对象B,
// 且从这个B子对象往上只有唯一一个C
// 因此结果pc2指回该C子对象。
C* pc2 = dynamic_cast<C*>(v); // 命中 8.1

// 验证
void* top = dynamic_cast<void*>(v);
std::cout << "address of m: " << &m << std::endl;
std::cout << "top: " << top << std::endl;
}

①v指向的最派生对象中,指的是包含v指向的这个基类子对象的那个完整体对象,即m
②v指向C对象的一个公有基类子对象,即B的对象

Paw5zx个人理解(针对8.2):点击查看更多

8.2对应的是侧向转型(crosscast),即在从同一个基类派生的两个类型之间进行转换

何时不会命中8.1?

  • ①v指向的B对象不是C的公有基类子对象,或;
  • ②B对象可以通过多条路径归属到C
1

9
如果v是空指针值,则结果是空指针值

7.6.1.8 Static cast

1
表达式 static_cast(v) 的结果,是将表达式v转换为类型T的结果。 - 若T是左值引用类型,或是指向函数的右值引用类型,则结果是一个左值; - 若T是指向对象的右值引用类型,则结果是一个将亡值; - 否则,结果是一个纯右值。

static_cast运算符不会去除常量性。

2
类型为"cv1 B"的左值(其中B是类类型)可以被转换为类型"cv2 D的引用"(其中D是从B派生的类),前提是cv2的限定符cv1相同,或者具有更高的限定等级。但如果B是D的虚基类,或是D的某个虚基类的基类,又或者从`D*`到`B*`不存在合法的标准转换(见[[conv.ptr]]()),那么程序为非法的(`ill-formed`)。

类型为”cv1 B”的将亡值,可以被转换为类型”cv2 D的右值引用”,所有限制条件与左值版”cv1 B”的转换(”cv1 B”的左值->”cv2 D的引用”)相同。

若”cv1 B”类型的对象实际上是某个D类型对象的基类子对象,则转换结果将引用该外围的D类型对象(包含这个B子对象的D对象本身)。否则(即B对象不是某个D的基类部分),行为未定义。

【示例:

1
2
3
4
5
6
struct B { };
struct D : public B { };
D d;
B &br = d;

static_cast<D&>(br); // produces lvalue denoting the original d object

——示例结束】

Paw5zx个人理解:点击查看更多

这里自己总结了一下,但是cppreference中总结的更好一些,因此参考cppreference中的总结:

对于static_cast<target-type>(expression),只能执行下列几种转换,除非这些转换会移除const(或volatile)限定:
1)如果expression是类型”cv1 Base”的左值,target-type是”cv2 Derived的引用”,并且满足以下约束,则结果指向expression所处的Derived类型对象:

  • Derived是一个完整类类型
  • Base是Derived的基类
  • cv2 ≥ cv1

如果满足以下条件,则程序是非法的:

  • Base是Derived的直接虚基类
  • Base是Derived的一个虚基类的基类
  • 不存在从””指向Derived的指针”到”指向Base的指针”的有效标准转换(访问性等因素)

如果expression实际上不是类型为Derived的对象的基类子对象,则行为是未定义的。

一些示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void func_virtual()
{
struct B {};
struct D : virtual B {}; // B是虚基类
B b;
// D& d = static_cast<D&>(b); // ill-formed
}

void func_no_inheritance()
{
struct B {};
struct D {}; // D不从B派生
B b;
// D& d = static_cast<D&>(b); // ill-formed
}

void func_private_inheritance()
{
struct B {};
struct D : private B {}; // 私有继承
B b;
// D& d = static_cast<D&>(b); // ill-formed
}

void func_not_subobject()
{
struct B {};
struct D : B {B b;};
D d;
B& br1 = d;
B& br2 = d.b;
static_cast<D&>(br1); // OK
// static_cast<D&>(br2); // b子对象不是基类子对象
}

void func_xvalue_conversion()
{
struct B {};
struct D : B {};
D d; // lvalue

D&& dr = static_cast<D&&>(static_cast<B&&>(d)); // 将亡值转换为右值引用
}
3

若“cv2 T2”与“cv1 T1”引用兼容(reference-compatible),则“cv1 T1”类型的左值可以被转换为“对cv2 T2的右值引用”类型。

若该左值不是位域,则结果引用该对象本身,或其指定的基类子对象;否则(即该左值是位域),将对该位域应用左值到右值转换(lvalue-to-rvalue conversion),并将得到的纯右值用作本小节其余部分的static_cast的表达式。

若T2是T1的不可访问或二义的基类,则需要进行此类转换的程序为非法的(ill-formed)。

Paw5zx个人理解:点击查看更多

否则(即该左值是位域),将对该位域应用左值到右值转换,并将得到的纯右值用作本小节其余部分的static_cast的表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void bitfield_case()
{
struct S {
unsigned bf : 3; // 位域
} s{5};

// 位域是lvalue,但不能生成对其的引用结果
// 标准:先做lvalue-to-rvalue conversion,得到一个prvalue(值拷贝)
// 再按prvalue → T2&&的规则,生成一个临时来绑定
//
unsigned&& r = static_cast<unsigned&&>(s.bf);

// r 绑定到一个**临时 unsigned**,不是 s.bf 本体
r = 0; // 修改的是临时,s.bf 不变
std::cout << "after r=0: s.bf = " << s.bf << ", r = " << r << "\n";
s.
}

若T2是T1的不可访问或二义的基类,则需要进行此类转换的程序为非法的(ill-formed)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void inaccessible_base_ill_formed()
{
struct B {};
struct D : private B {}; // 私有继承

D d;
// B&& br = static_cast<B&&>(d); // ill-formed:T2=B是T1=D的不可访问基类
}

void ambiguous_base_ill_formed()
{
struct B {};
struct X : B {};
struct Y : B {};
struct D : X, Y {}; // 到B有两条路径:X→B和Y→B

D d;
// B&& br = static_cast<B&&>(d); // ill-formed:T2=B是T1=D的二义基类
}