在C++语言中,如果类从包含虚函数的基类派生,则指向基类类型的指针可用于调用派生类对象中包含的虚函数的实现。 包含虚函数的类有时被称为“多态类”。
由于派生类完全包含它派生自的所有基类的定义,因此在类层次结构上将指针转换至这些基类中的任何一个是安全的。 提供一个指向基类的指针,在层次结构中向下转换指针可能是安全的。 如果将指向的对象实际上是从基类派生的类型,则是安全的。 在这种情况下,实际对象称为“完整对象”。指向基类的指针称为指向完整对象的“子对象”。 例如,考虑下图中显示的类层次结构。
类层次结构
如下图所示,可对类型为 C 的对象进行可视化。
带有 B 子对象和 A 子对象的类 C
给定 C 类的一个实例,存在 B 子对象和 A 子对象。 包括 A 和 B 子对象的 C 实例是“完整对象。”
通过使用运行时类型信息,可以检查指针实际是否指向完整对象,并可以安全转换以指向其层次结构中的另一个对象。 dynamic_cast 运算符可用于进行这些类型的转换。 它还执行必要的运行时检查以确保操作安全。
对于非多态类型的转换,可以使用 static_cast 运算符(本主题说明静态和动态强制转换之间的差异,以及何时适合使用它们)。
1、 c强制转换与c++强制转换
c语言强制类型转换主要用于基础的数据类型间的转换,语法为:
1
2
3
4 1(type-id)expression//转换格式1
2
3type-id(expression)//转换格式2
4
c++除了能使用c语言的强制类型转换外,还新增了四种强制类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast,主要运用于继承关系类间的强制转化,语法为:
1
2
3
4
5 1static_cast<new_type> (expression)
2dynamic_cast<new_type> (expression)
3const_cast<new_type> (expression)
4reinterpret_cast<new_type> (expression)
5
备注:new_type为目标数据类型,expression为原始数据类型变量或者表达式。
《Effective C++》中将c语言强制类型转换称为旧式转型,c++强制类型转换称为新式转型。
2、static_cast、dynamic_cast、const_cast、reinterpret_cast
a): static_cast 转换:用隐式和用户定义转换的组合在类型间转换。
语法
1
2 1static_cast < new_type > ( expression )
2
返回 new_type 类型的值。
唯有下列转换能用 static_cast 执行,除了这种转换会转型走常性或易变性的情况。
-
若有从 expression 到 new_type 的隐式转换序列,或若new_type 类型对象或引用的自 expression 的直接初始化会找到至少一个可生成函数,则 static_cast<new_type>(expression) 返回如同以 new_type Temp(expression); 初始化的虚构变量 Temp ,它可能涉及隐式转换、对 new_type 构造函数的调用或对用户定义转换运算符的调用。对于非引用的 new_type , static_cast 纯右值表达式的结果对象是被直接初始化者 (C++17 起)
-
若 new_type 是到某类类型 D 的指针或引用,且 expression 的类型是到其非虚基类 B 的指针或引用,则 static_cast 进行向下转型。若 B 是 D 的歧义、不可访问或虚基类(或虚基类的基类),则此向下转型病式。这种 static_cast 不进行运行时检查以确保该对象的运行时类型确实是 D ,而且仅若此前提条件为其他方法所保证才能安全使用,例如在实现静多态时。可以用 dynamic_cast 执行安全的向下转型。
3) 若 new_type 是右值引用类型,则 static_cast 转换泛左值、类纯右值或数组纯右值 (C++17 前)任何左值 (C++17 起) expression 的值为与该表达式指代相同对象,或指代其基类子对象(取决于 new_type )的亡值。若目标类型是表达式的不可访问或有歧义基类,则程序为病式。若表达式是位域左值,则它首先被转换成底层类型的纯右值。此类型的 static_cast 用于在 std::move 中实现移动语义。 | (C++11 起) |
1 | 1 |
-
若 new_type 是 void 类型(可为 cv 限定),则 static_cast 在求值 expression 后舍弃其值。
-
若存在从 new_type 到 expression 类型的标准转换序列,且它不包含左值到右值、数组到指针、函数到指针、空指针、空成员指针、函数指针 (C++17 起)或布尔转换,则 static_cast 能进行该隐式转换的逆转换。
-
若从 expression 到 new_type 设计左值到右值、数组到指针或函数到指针转换,则能显式用 static_cast 进行它。
-
有作用域枚举类型能转换成整数或浮点类型。当目标类型是 cv bool 时,若原值为零则结果为 false ,而对所有其他值结果为 true 。对于其余整数类型,若它们表示为目标类型,则结果是枚举的值,否则结果未指定。(C++11 起)
-
整数或枚举类型值能转换为任何完整枚举类型。
- 若底层类型不固定,则若 expression 的值落在范围(范围是大到足以保有目标枚举的所有枚举项的最小位域的所有可能值)外则结果未指定 (C++17 前)为未定义行为 (C++17 起)。
- 若底层类型固定,则结果同转换原值到枚举的底层类型再到该枚举类型的结果。
-
指向某类 D 成员的指针能向上转型为指向其无歧义、可访问的基类 B 。此 static_cast 不进行检查以确保成员实际存在于所指向对象的运行时类型。
-
指向(可有 cv 限定) void 指针类型的纯右值能转换到指向任何对象指针类型。若原指针值所表示的内存中字节地址不满足目标类型对齐要求,则结果指针值未指定。否则,若原指针值指向对象 a ,而有与 a 指针可互转换(定义于下)的目标类型(忽略 cv 限定)对象 b ,则结果为指向 b 的指针。否则指针值不改变。任何指针转换到指向 void 指针,再转换回指向原(或更为 cv 限定的)类型的指针,都保持其原值。
同所有转型表达式,结果是:
- 左值,若 new_type 是左值引用或到函数类型的右值引用;
- 亡值,若 new_type 是到对象类型的右值引用;
- 否则为纯右值。
二个对象 a 与 b 指针可互转换,若:
-
它们为同一对象,或
-
一个是 union 对象而另一个是该对象的非静态数据成员,或
-
一个是标准布局类对象,而另一个是该对象的首个非静态数据成员,或若该对象无非静态数据成员,则为该对象的首个基类子对象,或
-
存在对象 c 使得 a 与 c 指针可互转换,而 c 与 b 指针可互转换。
1
2
3
4
5 1union U { int a; double b; } u;
2void* x = &u; // x 的值为“指向 u 的指针”
3double* y = static_cast<double*>(x); // y 的值为“指向 u.b 的指针”
4char* z = static_cast<char*>(x); // z 的值为“指向 u 的指针”
5
注意:static_cast 亦可用于消歧义函数重载,通过进行到指定类型的函数到指针转换,如于 std::transform(s.begin(), s.end(), s.begin(), static_cast<int(*)(int)>(std::toupper));
运行此代码
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 1#include <vector>
2#include <iostream>
3
4struct B {
5 int m = 0;
6 void hello() const {
7 std::cout << "Hello world, this is B!\n";
8 }
9};
10struct D : B {
11 void hello() const {
12 std::cout << "Hello world, this is D!\n";
13 }
14};
15
16enum class E { ONE = 1, TWO, THREE };
17enum EU { ONE = 1, TWO, THREE };
18
19int main()
20{
21 // 1: 初始化转换
22 int n = static_cast<int>(3.14);
23 std::cout << "n = " << n << '\n';
24 std::vector<int> v = static_cast<std::vector<int>>(10);
25 std::cout << "v.size() = " << v.size() << '\n';
26
27 // 2: 静态向下转型
28 D d;
29 B& br = d; // 通过隐式转换向上转型
30 br.hello();
31 D& another_d = static_cast<D&>(br); // 向下转型
32 another_d.hello();
33
34 // 3: 左值到右值
35 std::vector<int> v2 = static_cast<std::vector<int>&&>(v);
36 std::cout << "after move, v.size() = " << v.size() << '\n';
37
38 // 4: 弃值表达式
39 static_cast<void>(v2.size());
40
41 // 5. 隐式转换的逆
42 void* nv = &n;
43 int* ni = static_cast<int*>(nv);
44 std::cout << "*ni = " << *ni << '\n';
45
46 // 6. 数组到指针后随向上转型
47 D a[10];
48 B* dp = static_cast<B*>(a);
49
50 // 7. 有作用域枚举到 int 或 float
51 E e = E::ONE;
52 int one = static_cast<int>(e);
53 std::cout << one << '\n';
54
55 // 8. int 到枚举,枚举到另一枚举
56 E e2 = static_cast<E>(one);
57 EU eu = static_cast<EU>(e2);
58
59 // 9. 指向成员指针向上转型
60 int D::*pm = &D::m;
61 std::cout << br.*static_cast<int B::*>(pm) << '\n';
62
63 // 10. void* 到任何类型
64 void* voidp = &e;
65 std::vector<int>* p = static_cast<std::vector<int>*>(voidp);
66}
67
输出:
1
2
3
4
5
6
7
8
9 1n = 3
2v.size() = 10
3Hello world, this is B!
4Hello world, this is D!
5after move, v.size() = 0
6*ni = 3
71
80
9
b): dynamic_cast 转换:沿继承层级向上、向下及侧向转换到类的指针和引用。
语法
1
2 1dynamic_cast < new_type > ( expression )
2
new_type | – | 指向完整类类型的指针、到完整类类型的引用,或指向(可选的 cv 限定) void 的指针 |
expression | – | 若 new_type 为引用,则为完整类类型的左值 (C++11 前)泛左值 (C++11 起)表达式,若 new_type 为指针,则为指向完整类类型的指针纯右值。 |
1 | 1 |
若转型成功,则 dynamic_cast 返回 new_type 类型的值。若转型失败且 new_type 是指针类型,则它返回该类型的空指针。若转型失败且 new_type 是引用类型,则它抛出匹配类型 std::bad_cast 处理块的异常。
解释
仅下列转换能用 dynamic_cast 进行,除了这种转换会转换走常性或易变性的情况。
-
若 expression 的类型恰是 new_type 或 new_type 的较少 cv 限定版本,则结果是 expression 带 new_type 类型的值。(换言之, dynamic_cast 可用以添加常性。隐式转型和 static_cast 亦能进行此转换。)
-
若 expression 是空指针值,则结果是 new_type 类型的空指针值。
-
若 new_type 是到 Base 的指针或引用,且 expression 的类型是到 Derived 的指针或引用,其中 Base 是 Derived 的单一可访问基类,则结果是到 expression 所标识的对象中 Base 类子对象的指针或引用。(注意:隐式转型和 static_cast 亦能进行此转换。)
-
若 expression 是指向多态类型的指针,且 new_type 是到 void 的指针,则结果是指向 expression 所指向或引用对象的最终派生类的指针。
-
若 expression 是到多态类型 Base 的指针或引用,且 new_type 是到 Derived 类型的指针或引用,则进行运行时检查:
a) 检验 expression 所指向/标识的最终派生类对象。若在该对象中 expression 指向/指代 Derived 的公开基类,且只有一个 Derived 类型子对象从 expression 所指向/标识的子对象派生,则转型结果指向/指代该 Derived 子对象。(此之谓“向下转型”。)
b) 否则,若 expression 指向最终派生类的公开基类,而同时最终派生类拥有 Derived 类型的无歧义公开基类,则转型结果指向/指代该 Derived (此之谓“侧向转型”。)
c) 否则,运行时检查失败。若 dynamic_cast 用于指针,则返回 new_type 类型空指针值。若它用于引用,则抛出 std::bad_cast 异常。
- dynamic_cast 用于构造函数或析构函数(直接或间接),且 expression 指代正在构造/析构的对象时,该对象被认为是最终派生对象。若 new_type 不是到构造函数/析构函数自身的类或其基类之一的指针,则行为未定义。
同其他转型表达式,结果是:
- 左值,若 new_type 是左值引用类型( expression 必须是左值)
- 亡值,若 new_type 是右值引用类型( expression 为完整类类型,可以是左值或右值 (C++17 前)必须是泛左值(纯右值被实质化) (C++17 起))
- 纯右值,若 new_type 是指针类型
注意
向下转型亦可用 static_cast 进行,它避免运行时检查的开销,但它仅若程序能保证(通过某些其他逻辑) 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
44
45
46
47
48
49
50
51 1#include <iostream>
2
3struct V {
4 virtual void f() {}; // 必须为多态以使用运行时检查的 dynamic_cast
5};
6struct A : virtual V {};
7struct B : virtual V {
8 B(V* v, A* a) {
9 // 构造中转型(见后述 D 的构造函数中的调用)
10 dynamic_cast<B*>(v); // 良好定义: v 有类型 V* , B 的 V 基类,产生 B*
11 dynamic_cast<B*>(a); // 未定义行为: a 有类型 A* , A 非 B 的基类
12 }
13};
14struct D : A, B {
15 D() : B((A*)this, this) { }
16};
17
18struct Base {
19 virtual ~Base() {}
20};
21
22struct Derived: Base {
23 virtual void name() {}
24};
25
26int main()
27{
28 D d; // 最终派生类
29 A& a = d; // 向上转型,可以用 dynamic_cast ,但不必须
30 D& new_d = dynamic_cast<D&>(a); // 向下转型
31 B& new_b = dynamic_cast<B&>(a); // 侧向转型
32
33
34 Base* b1 = new Base;
35 if(Derived* d = dynamic_cast<Derived*>(b1))
36 {
37 std::cout << "downcast from b1 to d successful\n";
38 d->name(); // 调用安全
39 }
40
41 Base* b2 = new Derived;
42 if(Derived* d = dynamic_cast<Derived*>(b2))
43 {
44 std::cout << "downcast from b2 to d successful\n";
45 d->name(); // 调用安全
46 }
47
48 delete b1;
49 delete b2;
50}
51
输出:
1
2 1downcast from b2 to d successful
2
c): const_cast 转换:在有不同 cv 限定的类型间转换。
语法
1
2 1const_cast < new_type > ( expression )
2
返回 new_type 类型的值。
解释
只能以 const_cast 进行下列转换。特别是,唯有 const_cast 可用于转型掉(移除)常性或易变性。
-
二个指向同一类型的可能多级的指针可以互相转换,无关乎每个层级的 cv 限定符。
-
任何 T 类型左值可转换为到同一类型 T 的左值或右值引用, cv 限定可更多或更少。同样地,右值可转换成更多或更少 cv 限定的右值引用。引用 const_cast 的结果指代原对象,若 expression 是泛左值,否则指代实质化的临时量 (C++17 起)。
-
同样的规则应用于可能多层的到数据成员的指针及可能多层的到已知和未知边界数组( cv 限定元素的数组被认为是自身亦有 cv 限定) (C++17 起)
-
空指针值可转换成 new_type 的空指针值
同所有转型表达式,结果是:
- 左值,若 new_type 是左值引用或到函数类型的右值引用;
- 亡值,若 new_type 是到对象类型的右值引用;
- 否则为纯右值。
注意:指向函数指针和指向成员函数指针不可用于 const_cast
const_cast 使得能够组成实际指代 const 对象 的到非 const 类型的引用或指针,或组成实际指代 volatile 对象的到非 volatile 类型的引用或指针。通过非 const 访问路径修改 const 对象和通过非 volatile 泛左值指代 volatile 对象是未定义行为。
运行此代码
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 1#include <iostream>
2
3struct type {
4 int i;
5
6 type(): i(3) {}
7
8 void f(int v) const {
9 // this->i = v; // 编译错误: this 是指向 const 的指针
10 const_cast<type*>(this)->i = v; // 只要该对象不是 const 就 OK
11 }
12};
13
14int main()
15{
16 int i = 3; // 不声明 i 为 const
17 const int& rci = i;
18 const_cast<int&>(rci) = 4; // OK :修改 i
19 std::cout << "i = " << i << '\n';
20
21 type t; // 假如这是 const type t ,则 t.f(4) 会是未定义行为
22 t.f(4);
23 std::cout << "type::i = " << t.i << '\n';
24
25 const int j = 3; // 声明 j 为 const
26 int* pj = const_cast<int*>(&j);
27 // *pj = 4; // 未定义行为
28
29 void (type::* pmf)(int) const = &type::f; // 指向成员函数的指针
30 // const_cast<void(type::*)(int)>(pmf); // 编译错误: const_cast 不在成员函数指针上工作
31}
32
输出:
1
2
3 1i = 4
2type::i = 4
3
d): reinterpret_cast 转换:通过转译底层位模式在类型间转换。
语法
1
2 1reinterpret_cast < new_type > ( expression )
2
返回 new_type 类型的值。
解释
与 static_cast 不同,但与 const_cast 类似, reinterpret_cast 表达式不会编译成任何 CPU 指令(除非在整数和指针间转换,或在指针表示依赖其类型的不明架构上)。它纯粹是一个编译时指令,指示编译器将 expression 的位序列(对象表示)视为 new_type 类型的位序列。
仅下列转换能用 reinterpret_cast 进行,除了这种转换会转型走常性或易变性的情况。
-
整数、枚举、指针或指向成员指针类型表达式能转换到其自身的类型。产生的值与 expression 的相同。(C++11 起)
-
指针能转换成大到足以保有其类型所有值的任何整数类型(例如转换成 std::uintptr_t )
-
任何整数或枚举类型能转换到指针类型。指针转换到有足够大小的整数再转换回同一指针类型后,保证拥有其原值,否则结果指针无法安全解引用(反向的来回转换不保证;相同指针可拥有多种整数表示)。空指针常量 NULL 或整数不保证生成目标类型的空指针值;为此目的应该用 static_cast 或隐式转换。
-
任何 std::nullptr_t 类型值,包含 nullptr ,能转换成任何整数类型,如同它是 (void*)0 ,但没有值能转换成 std::nullptr_t ,甚至 nullptr 也不行:为该目的应该用 static_cast 。(C++11 起)
-
任何指向 T1 类型对象的指针能转换成指向另一类型 cv T2 对象的指针。这准确地等价于 static_cast<cv T2*>(static_cast<cv void*>(expression)) (这隐含若 T2 的对齐要求不严格于 T1 的,则指针值不改变,且结果指针转换回原类型生成原值)。任何情况下,若类型别名使用规则允许,结果指针才可以安全地解引用(见后述)
-
T1 类型的左值表达式能转换成到 T2 类型的引用。结果是与原左值指代同一对象,但有不同类型的左值或亡值。不创建临时量,不进行复制,不调用构造函数或转换函数。若类型别名使用规则允许,结果引用才能安全访问(见后述)
-
任何指向函数指针能转换成指向不同函数类型的指针。通过指向相异函数类型的指针调用函数是未定义的,但转换这种指针回指向原函数的指针类型生成指向原函数的指针值。
-
一些实现上(特别是在任何 POSIX 兼容的系统上,因为被 dlsym 要求),函数指针能转换成 void* 或任何其他对象指针,反之亦然。若实现支持双向的转换,则转换回原类型生成原值,否则结果指针不能安全地解引用或调用。
-
任何指针类型的空指针值能转换成任何其他指针类型,产生该类型的空指针值。注意不能用 reinterpret_cast 转换空指针常量 nullptr 或任何其他 std::nullptr_t 类型的值为指针:为此目的应该使用隐式转换或 static_cast 。
-
指向成员函数的指针右值能转换成指向不同类型的不同成员函数。转换回原类型生成原值,否则结果指针不能安全使用。
-
指向某类 T1 成员对象的指针右值能转换成指向另一类 T2 的另一成员对象的指针。若 T2 的对齐不严格于 T1 ,则转换到原类型生成原值,否则结果指针不能安全使用。
同所有转型表达式,结果是:
- 左值,若 new_type 是左值引用或到函数类型的右值引用;
- 亡值,若 new_type 是到对象类型的右值引用;
- 否则为纯右值。
类型别名使用
凡在试图通过 AliasedType 类型泛左值读或修改拥有 DynamicType 类型对象的值时,行为未定义,除非下列之一为真:
- AliasedType 与 DynamicType 相似。
- AliasedType 是 DynamicType 的(可有 cv 限定的)有符号或无符号变体。
- AliasedType 为 std::byte 、 (C++17 起)char 或 unsigned char :这容许检验将对象的对象表示作为字符数组检验。
非正式地说,忽略顶层 cv 限定(但不含在函数类型内者),二个类型符合下列条件,则原类型相似:
- 它们是同一类型;或
- 它们都是指针,而被指向的类型相似;或
- 它们都是指向相同类的成员指针,而被指向的成员类型相似;或
- 它们都是大小相同的数组或都是未知边界数组,而数组元素类型相似。
例如:
- const int * volatile * 与 int * * const 相似;
- const int (* volatile S::* const)[20] 与 int (* const S::* volatile)[20] 相似;
- int (* const *)(int *) 与 int (* volatile *)(int *) 相似;
- int (S::*)() const 与 int (S::*)() 不相似;
- int (*)(int *) 与 int (*)(const int *) 不相似;
- const int (*)(int *) 与 int (*)(int *) 不相似;
- int (*)(int * const) 与 int (*)(int *) 相似(它们是同一类型);
- std::pair<int, int> 与 std::pair<const int, int> 不相似。
此规则启用基于类型的别名分析,其中编译器假设通过一个类型的泛左值读取的值,不会为对不同类型的泛左值的写入所修改(受上述例外影响)。
注意,许多 C++ 编译器作为非标准语言扩展放松此规则,以允许通过 union 不活跃成员的错误类型访问(这种访问在 C 中不是未定义)。
注意:
标准中定义严格别名使用规则的段落含有二个从 C 继承的额外条例:
- AliasedType 为聚合类型或 union 类型,它保有作为一个作为元素或非静态成员的前述类型(递归地包含子聚合体的元素和被含有联合体的非静态数据成员)。
- AliasedType 为 DynamicType 的(可有 cv 限定的)基类。
这些条例所描述的情况不可能出现于 C++ ,从而从上面的讨论省略。 C 中,聚合复制和赋值将聚合体对象作为作为整体访问。但 C++ 中始终通过成员函数调用进行这种行动,这会访问单独的子对象,而非整个对象(或在联合体的情况下,复制对象表示,即经由 unsigned char )。见核心问题 2051 。
假设符合对齐要求,则 reinterpret_cast 在处理指针可互转换对象的少数受限情况外,不更改指针的值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1struct S1 { int a; } s1;
2struct S2 { int a; private: int b; } s2; // 非标准布局
3union U { int a; double b; } u = {0};
4int arr[2];
5
6int* p1 = reinterpret_cast<int*>(&s1); // p1 的值为“指向 s1.a 的指针”
7 // 因为 s1.a 与 s1 为指针可互转换
8
9int* p2 = reinterpret_cast<int*>(&s2); // reinterpret_cast 不更改 p2 的值为“指向 p2 的指针”。
10
11int* p3 = reinterpret_cast<int*>(&u); // p3 的值为“指向 u.a 的指针”: u.a 与 u 指针可互转换
12
13double* p4 = reinterpret_cast<double*>(p3); // p4 的指针为“指向 u.b 的指针”: u.a 与 u.b
14 // 指针可互转换,因为都与 u 指针可互转换
15
16int* p5 = reinterpret_cast<int*>(&arr); // reinterpret_cast 不更改 p5 的值为“指向 arr 的指针”
17
在不实际指代适当类型对象的泛左值上,进行指代非静态数据成员或非静态成员函数的成员访问——例如通过 reinterpret_cast 获得者——导致未定义行为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 1struct S { int x; };
2struct T { int x; int f(); };
3struct S1 : S {}; // 标准布局
4struct ST : S, T {}; // 非标准布局
5
6S s = {};
7auto p = reinterpret_cast<T*>(&s); // p 的值为“指向 s 的指针”
8auto i = p->x; // 类成员访问表达式为未定义行为: s 不是 T 对象
9p->x = 1; // 未定义行为
10p->f(); // 未定义行为
11
12S1 s1 = {};
13auto p1 = reinterpret_cast<S*>(&s1); // p1 的值为“指向 S 的 s1 子对象的指针”
14auto i = p1->x; // OK
15p1->x = 1; // OK
16
17ST st = {};
18auto p2 = reinterpret_cast<S*>(&st); // p2 的值为“指向 st 的指针”
19auto i = p2->x; // 未定义行为
20p2->x = 1; // 未定义行为
21
许多编译器在这种情况下发布“严格别名使用”警告,即使在技术性地通过某种构造,违背异于通称为“严格别名使用规则”的段落的情况下。
严格别名使用和相关规则的目的是启用基于类型的别名分析,若程序能合法地创建情形,使得二个指向无关类型的指针(例如一个 int* 和一个 float* )能同时存在并可一同用于加载或存储同一内存(见此 SG12 reflector 上的邮件),则别名分析会普遍无效。故任何看起来能够创建这种情形的技巧都需要引发未定义行为。
需要转译对象的字节为不同类型的值时,能使用 std::memcpy 或 std::bit_cast (C++20 起):
1
2
3
4
5
6
7 1double d = 0.1;
2std::int64_t n;
3static_assert(sizeof n == sizeof d);
4// n = *reinterpret_cast<std::int64_t*>(&d); // 未定义行为
5std::memcpy(&n, &d, sizeof d); // OK
6n = std::bit_cast<std::int64_t>(d); // 亦 OK
7
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
CWG 195
C++98
不允许函数指针和对象指针间的转换
使之为条件性支持
演示 reinterpret_cast 的一些用法:
运行此代码
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 1#include <cstdint>
2#include <cassert>
3#include <iostream>
4int f() { return 42; }
5int main()
6{
7 int i = 7;
8
9 // 指针到整数并转回
10 std::uintptr_t v1 = reinterpret_cast<std::uintptr_t>(&i); // static_cast 为错误
11 std::cout << "The value of &i is 0x" << std::hex << v1 << '\n';
12 int* p1 = reinterpret_cast<int*>(v1);
13 assert(p1 == &i);
14
15 // 到另一函数指针并转回
16 void(*fp1)() = reinterpret_cast<void(*)()>(f);
17 // fp1(); 未定义行为
18 int(*fp2)() = reinterpret_cast<int(*)()>(fp1);
19 std::cout << std::dec << fp2() << '\n'; // 安全
20
21 // 通过指针的类型别名使用
22 char* p2 = reinterpret_cast<char*>(&i);
23 if(p2[0] == '\x7')
24 std::cout << "This system is little-endian\n";
25 else
26 std::cout << "This system is big-endian\n";
27
28 // 通过引用的类型别名使用
29 reinterpret_cast<unsigned int&>(i) = 42;
30 std::cout << i << '\n';
31
32 [[maybe_unused]] const int &const_iref = i;
33 // int &iref = reinterpret_cast<int&>(const_iref); // 编译错误——不能去除 const
34 // 必须用 const_cast 代替: int &iref = const_cast<int&>(const_iref);
35}
36
可能的输出:
1
2
3
4
5 1The value of &i is 0x7fff352c3580
242
3This system is little-endian
442
5
3、显式类型转换:用显式和隐式转换的组合在类型间转换。
语法
( new_type ) expression | (1) | |
new_type ( expression ) | (2) | |
new_type ( expressions ) | (3) | |
new_type ( ) | (4) | |
new_type { expression-list(可选) } | (5) | (C++11 起) |
template ( expressions(可选) ) | (6) | (C++17 起) |
template { expressions(可选) } | (7) | (C++17 起) |
1 | 1 |
返回 new_type 类型值。
解释
- 遇到 C 风格转型表达式时,编译器试图将它转译成下列转型表达式,以此顺序:
a) const_cast<new_type>(expression);
b) static_cast<new_type>(expression) ,带扩展:到导出类的引用或指针额外允许转型成到无歧义基类的引用或指针(反之亦然),纵使基类不可访问(即此转型忽略 private 继承指定符)。同样适用于转型指向成员指针到指向无歧义非虚基类的成员指针;
c) static_cast (带扩展)后随 const_cast ;
d) reinterpret_cast<new_type>(expression) ;
e) reinterpret_cast 后随 const_cast 。
选择首个满足各自转型运算符要求的首选项,即使它无法编译(见示例)。若转型能转译成多于一种 static_cast 后随 const_cast 的方式,则它无法编译。
另外, C 风格转型记号允许在不完整类型的指针之间双向转型。若 expression 与 new_type 是指向不完整类型的指针,则 static_cast 还是 reinterpret_cast 得到选择是未指定的。
-
函数式转型表达式由一个简单类型指定符或 typedef 指定符(换言之,是单个词的类型名: unsigned int(expression) 或 int*(expression) 非法),后随括号中的单个表达式。此转型表达式准确等价于对应的 C 风格转型表达式。
-
若括号中有多于一个表达式,则 new_type 必须是有适合声明的构造函数的类。此表达式是 new_type 类型纯右值,其指代的临时量 (C++17 前)其结果对象 (C++17 起)以 expressions 直接初始化。
-
若 new_type 指名一个非数组完整对象类型,则此表达式是 new_type 类型纯右值,指代该类型临时量 (C++17 前)其结果对象(可以有 cv 限定)为该类型 (C++17 起)。若 new_type 是对象类型,则对象被值初始化。若 new_type 是(可有 cv 限定的) void ,则表达式是 void 纯右值而无结果对象 (C++17 起)。
-
单个词的类型名后随花括号初始化器列表,是指定类型的纯右值,其指代的临时量 (C++17 前)其结果对象 (C++17 起)以指定的花括号初始化器列表直接列表初始化。这是仅有的能创建数组纯右值的表达式。
6,7) 同 (2-5) ,除了首先进行类模板实参推导,
同所有转型表达式,结果是:
- 左值,若 new_type 是左值引用或到函数类型的右值引用;
- 亡值,若 new_type 是到对象类型的右值引用;
- 否则为纯右值。
运行此代码
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 1double f = 3.14;
2unsigned int n1 = (unsigned int)f; // C 风格转型
3unsigned int n2 = unsigned(f); // 函数式转型
4
5class C1;
6class C2;
7C2* foo(C1* p)
8{
9 return (C2*)p; // 转型不完整类型到不完整类型
10}
11
12// 此示例中, C 风格转型被转译成 static_cast
13// 尽管它也能作为 reinterpret_cast 工作
14struct A {};
15struct I1 : A {};
16struct I2 : A {};
17struct D : I1, I2 {};
18
19int main()
20{
21 D* d = nullptr;
22// A* a = (A*)d; // 编译时错误
23 A* a = reinterpret_cast<A*>(d); // 这能编译
24}
25
4、隐式转换
凡是在语境中使用了某种表达式类型 T1,但语境不接受该类型,而接受另一类型 T2 的时候,会进行隐式转换,具体是:
- 调用以 T2 为参数声明函数时,以该表达式为参数;
- 运算符期待 T2 ,而以该表达式为运算数;
- 初始化 T2 类型新对象,包括在返回 T2 的函数中的 return 语句;
- 将表达式用于 switch 语句( T2 是整数类型);
- 将表达式用于 if 语句或循环( T2 是 bool )。
程序为良式(能编译),仅若存在一个从 T1 到 T2 的无歧义隐式转换序列。
若有多个函数或运算符的重载会被调用,则在从 T1 到每个可用的 T2 构造隐式转化序列后,重载决议规则决定编译哪个重载。
注意:算术表达式中,给二元运算符上的运算数上的隐式转换目标类型由一组有别的规则,通常算术转换决定。
转换顺序
隐式转换序列由下列内容构成,以此顺序:
-
零或一个标准转换序列;
-
零或一个用户定义转换;
-
零或一个标准转换序列。
考虑给构造函数或用户定义转换函数的参数时,只允许一个标准转换序列(否则能等效地连锁用户定义序列)。从一个内建类型转换到另一内建类型时,只允许一个标准转换序列。
标准转换序列由下列内容构成,以此顺序:
-
零或一个左值变换;
-
零或一个数值提升或数值转换;
3) 零或一个函数指针转换; | (C++17 起) |
1 | 1 |
- 零或一个限定调整。
用户定义转换由零或一个非 explicit 单实参构造函数或非 explicit 转换函数调用构成。
若且唯若 T2 能从表达式 e 复制初始化,即对于某虚设的临时对象 t ,声明 T2 t = e; 为良式(能编译),才说表达式 e 可隐式转换为 T2 。注意这有别于直接初始化( T2 t(e) ),其中会额外考虑 explicit 构造函数和转换函数。
按语境转换
下列语境中,期待类型 bool ,而若声明 bool t(e); 为良式则进行隐式转换(即考虑如 explicit T::operator bool() const; 的隐式转换函数)。我们说这种表达式 e 可按语境转换为 bool。 if 、 while 、 for 的控制表达式; 内建逻辑运算符 ! 、 && 和 || 的运算数; 条件运算符 ?: 的首个运算数; static_assert 声明中的谓词; noexcept 指定符中的表达式; explicit 指定符中的表达式; 契约属性的谓词。 (C++20 起) | explicit 指定符中的表达式; 契约属性的谓词。 | (C++20 起) | (C++11 起) |
explicit 指定符中的表达式; 契约属性的谓词。 | (C++20 起) |
1 | 1 |
下列语境中,期待语境限定类型 T ,而类类型 E 表达式 e 仅若E 拥有单个非 explicit 用户定义转换函数以转换到可允许类型 (C++14 前)可允许类型中恰好有一个类型 T ,满足 E 拥有非 explicit 转换函数,其返回类型为(可有 cv 限定的) T 或到(可有 cv 限定的) T 的引用,且 e 可隐式转换为 T (C++14 起)才得到允许。我们说这种表达式 e 可按语境隐式转换到指定的类型 T 。注意不考虑 explicit 转换函数,即使在按语境转换到 bool 中考虑他们。 (C++11 起)
-
delete 表达式( T 是任何对象指针类型);
-
整数常量表达式,其中使用字面类( T 是任何整数或无作用域枚举类型,被选择用户定义转换函数必须是 constexpr );
-
switch 语句的控制表达式( T 是整数或枚举类型)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 1#include <cassert>
2
3template<typename T>
4class zero_init
5{
6 T val;
7public:
8 zero_init() : val(static_cast<T>(0)) { }
9 zero_init(T val) : val(val) { }
10 operator T&() { return val; }
11 operator T() const { return val; }
12};
13
14int main()
15{
16 zero_init<int> i; assert(i == 0);
17 i = 7; assert(i == 7);
18 switch(i) { } // C++14 前错误(多于一个转换函数)
19 // C++14 (二个函数转换到同一类型 int )
20 switch(i + 0) { } // 始终 OK (隐式转换)
21}
22
值变换
值变换是更改表达式值类别的转换。凡是表达式作为期待不同值类别表达式的运算符的运算数时,值变换发生。
左值到右值转换
任何非函数、非数组类型 T 的泛左值能隐式转换成同类型的纯右值。若 T 为非类类型,则此转换亦移除 cv 限定符。若泛左值拥有 std::nullptr_t 类型,则作为结果的纯右值是空指针常量 nullptr 。
除非遇到不求值语境( sizeof 、 typeid 、 noexcept 或 decltype 的运算数中),此转换等效地以原泛左值为构造函数参数,复制构造 T 临时对象,然后将该临时对象作为纯右值返回。
此转换模仿从内存位置读取值到 CPU 寄存器中的行动。
若泛左值所指代的对象含有不确定值(例如由默认初始化非类类型静态变量获得),则行为未定义
除非不确定值拥有可为 cv 限定的无符号字符类型,且不缓存于 CPU 寄存器,或者正式而言: 其存储期是静态或线程局域; 或已构造指向它的指针; 或已将它绑定到引用。 若泛左值含有为 delete 所非法化的指针值,则行为亦为实现定义(而非未定义)。 | (C++11 起) |
1 | 1 |
数组到指针转换
“ N 个 T 的数组”或“ T 的未知边界数组”类型的左值或右值能隐式转换为“指向 T 的指针”类型的纯右值。 若数组是纯右值,则发生临时量实质化。 (C++17 起)产生的指针指向数组首元素(细节参阅数组到指针退化)。
临时量实质化 任何完整类型 T 的纯右值能转换为同类型 T 的亡值。此转换从纯右值初始化 T 类型临时对象,通过以临时对象为结果对象求值该纯右值。 若 T 是类类型或类类型数组,则它必须有可访问且未被删除的析构函数 | |
struct S { int m; }; | |
int k = S().m; // C++17 起成员访问期待泛左值; |
1
2 1 // 转换 S() 纯右值为亡值 临时量实质化在下例情形出现: 绑定引用到右值时; 在类右值上进行成员访问时; 进行数组到指针转换(见上述)或在数组纯右值上使用下标时; 从花括号初始化器列表初始化 std::initializer_list<T> 类型对象时; 应用 typeid 到纯右值时(这是不求值表达式的一部分); 应用 sizeof 到纯右值时(这是不求值表达式的一部分); 纯右值出现作弃值表达式时; |(C++17 起) |
2
1 | 1 |
函数到指针
函数类型 T 的左值能隐式转换成指向该函数的指针纯右值。这不作用于非静态成员函数,因为不存在指代非静态成员函数的左值。
数值提升
整数类型提升
小整数类型(如 char )的纯右值能转换为较大整数类型(如 int )的纯右值。具体而言,算术运算符不接受小于 int 的类型为参数,而在左值到右值转换后自动应用整数提升,若它可应用。此转换始终保持原值。
以下类型转换被分类为是整数类型提升:
- signed char 或 signed short 能转换为 int ;
- unsigned char 或 unsigned short 能转换为 int ,若 int 能保有其整个值范围,否则能转换为 unsigned int ;
- char 能转换为 int 或 unsigned int ,取决于底层类型: signed char 或 unsigned char (见上述);
- wchar_t 、 char16_t 及 char32_t 能转换为以下列表中能保有其整个值范围的首个类型: int 、 unsigned int 、 long 、 unsigned long 、 long long 、 unsigned long long ;
- 底层类型不固定的无作用域枚举类型能转换为以下列表中能保有其整个值范围的首个类型: int 、 unsigned int 、 long 、 unsigned long 、 long long 、 unsigned long long 、扩展整数类型(以大小顺序,有符号优先于无符号) (C++11 起)。若值范围更大,则不应用整数类型提升;
底层类型固定的无作用域枚举类型能转换为其底层类型,而若底层类型亦可进行整数类型提升,则还有提升后的底层类型。到未提升的底层类型的转换对于重载决议的目的更优; | (C++11 起) |
1 | 1 |
- 位域类型能转换为 int ,若 int 能表示位域的整个值范围,否则能转换为 unsigned int ,若 unsigned int 能表示位域的整个值范围,否则不应用整数类型提升;
- bool 类型能转换为 int ,值 false 变为 0 而 true 变为 1 。
注意所有其他转换都不是提升;例如重载决议选择 char -> int (提升)优先于 char -> short (转换)。
浮点类型提升
float 类型纯右值能转换为 double 类型值。值不更改。
数值转换
不同于提升,数值转换可以更改值,而且有潜在的精度损失。
整数类型转换
任何整数类型或无作用域枚举类型的纯右值能隐式转换成任何其他整数类型。若转换列在整数类型提升下,则它是提升而非转换。
- 若目标类型为无符号,则结果值是等于源值模 2n
的最小无符号值,其中 n 是用于表示目标类型的位数。
即取决于目标类型更宽或更窄,分别符号扩展[脚注 1]或截断有符号数,而零扩展或截断无符号数。
- 若目标类型有符号,则若源整数能以目标类型表示,则不更改其值。否则结果是实现定义的(注意这异于未定义的有符号整数算术溢出)。
- 若源类型为 bool ,则值 false 转换为目标类型的零,而值 true 转换成目标类型的一(注意若目标类型为 int ,则这是整数类型提升,而非整数类型转换)。
- 若目标类型为 bool ,则这是布尔转换(见后述)。
浮点类型转换
浮点类型纯右值能转换成任何其他浮点类型的纯右值。若转换列于浮点类型提升下,则它是提升而非转换。
- 若源值能以目标类型准确表示,则不更改它。
- 若原值在目标类型的二个可表示值之间,则结果是二个值之一(选择哪个是实现定义的,不过若支持 IEEE ,则舍入默认为到最接近)。
- 否则,行为未定义。
浮点整数转换
- 浮点类型纯右值能隐式转换成任何整数类型的纯右值。截断小数部分,即舍弃小数部分。若结果不能适应到目标类型中,则行为未定义(即使在目标类型为无符号数时,也不应用模算术)。若目标类型为 bool ,则这是布尔转换(见后述)。
- 无作用域枚举类型或整数类型的纯右值能转换成任何浮点类型的纯右值。若不能正确表示该值,则选择最接近较高值还是最接近的较低值是实现定义的,不过若支持 IEEE ,则舍入默认为到最接近。若值不能适应到目标类型中,则行为未定义。若源类型为 bool ,则值 false 转换为零,而值 true 转换为一。
指针转换
- 空指针常量(见 NULL )能转换成任何指针类型,而结果是该类型的空指针值。允许这种转换(称为空指针转换) 作为单次转换,转换到 cv 限定类型,即不认为它是数值和限定转换的结合。
- 指向任何(可有 cv 限定的)对象类型 T 的指针纯右值能转换成指向(有等同 cv 限定的) void 的指针纯右值。结果指针与原指针表示内存中的同一位置。若原指针是空指针值,则结果为目标类型的空指针值。
- 指向派生类类型的(可有 cv 限定的)空指针能转换成指向其(有等同 cv 限定的)基类的指针。若基类不可访问或有歧义,则转换为病式(不能编译)。转换结果是指向原被指向对象内的基类子对象的指针。空指针值转换成目标类型的空指针值。
成员指针转换
- 空指针常量(见 NULL )能转换成任何指向成员指针类型,而结果是该类型的空成员指针值。允许这种转换(成为空成员指针转换)作为单次转换,转换到 cv 限定类型,即不认为它是数值和限定转换的结合。
- 指向基类 B 中某类型 T 成员的指针纯右值能转换成指向其派生类 D 中同一类型 T 成员的指针纯右值。若 B 是 D 的间接、有歧义或虚基类或是 D 的某个中间虚基类的基类,则转换为病式(不能编译)。能以 D 对象解引用结果指针,而它将访问该 D 对象的 B 基类子对象内的成员。空成员指针值转换成目标类型的空成员指针值。
布尔转换
整数、浮点、无作用域枚举、指针和指向成员指针类型的纯右值能转换成 bool 类型纯右值。
值零(对于整数、浮点和无作用域枚举)、空指针值和空成员指针值变为 false 。所有其他值变为 true 。
std::nullptr_t 类型纯右值,包括 nullptr ,能在直接初始化的语境中转换成 bool 类型纯右值。结果值为 false 。 | (C++11 起) |
1 | 1 |
限定转换
- 指向有 cv 限定的类型 T 的指针类型纯右值能转换为指向有更多 cv 限定的同一类型 T 的指针纯右值(换言之,能添加常性和易变性)。
- 指向类 X 中有 cv 限定的类型 T 的指向成员指针纯右值能转换成指向类 X 中有更多 cv 限定的类型 T 的指向成员指针纯右值。
“更多” cv 限定表明
- 指向无限定类型的指针能转换成指向 const 的指针;
- 指向无限定类型的指针能转换成指向 volatile 的指针;
- 指向无限定类型的指针能转换成指向 const volatile 的指针;
- 指向 const 类型的指针能转换成指向 const volatile 的指针;
- 指向 volatile 类型的指针能转换成指向 const volatile 的指针。
对于多级指针,应用下列限制:身为 cv1
0 限定指针,指向 cv1
1 限定指针,指向…… cv1
n-1 限定指针,指向 cv1
n 限定 T 的多级指针 P1 ,可转换成身为 cv2
0 限定指针,指向 cv2
1 限定指针,指向…… cv2
n-1 限定指针,指向 cv2
n 限定 T 的多级指针 P2 ,仅若
- 两个指针的级数 n 相同;
- 若在 P1 的某级(除了零级)的 cv1
k 中有 const ,则在 P2 的同级 cv2
k 中有 const ;
- 若在 P1 的某级(除了零级)的 cv1
k 中有 volatile ,则在 P2 的同级 cv2
k 中有 volatile ;
- 若在某级 k 上, P2 比 P1 有更多 cv 限定,则 P2 到 k 为止的每一级(除了零级) cv2
1, cv2
2 … cv2
k 上都必须有 const 。
- 同样的规则用于指向成员的多级指针及指向对象和指向成员的多级混合指针;
同样的规则用于包含在任何级指向已知边界或未知边界数组(认为有 cv 限定元素的数组自身有等同的 cv 限定)的多级指针; | (C++14 起) |
1 | 1 |
-
零级由非多级限定转换的规则处理。
1
2
3
4
5
6
7
8
9 1char** p = 0;
2const char** p1 = p; // 错误: 2 级有更多 cv 限定但 1 级非 const
3const char* const * p2 = p; // OK : 2 级有更多 cv 限定并在 1 级添加 const
4volatile char * const * p3 = p; // OK : 2 级更有 cv 限定并在 1 级添加 const
5volatile const char* const* p4 = p2; // OK : 2 级更有 cv 限定而 const 已在 1 级
6
7double *a[2][3];
8double const * const (*ap)[3] = a; // C++14 起 OK
9
注意 C 编程语言中,只能添加 const/volatile 到第一级:
1
2
3
4 1char** p = 0;
2char * const* p1 = p; // C 与 C++ 中 OK
3const char* const * p2 = p; // C 中错误, C++ 中 OK
4
函数指针转换 指向不抛出函数的指针类型的纯右值能转换成指向潜在抛出函数的指针纯右值。 指向不抛出成员函数指针类型的纯右值能转换成指向潜在抛出函数指针的纯右值。 | |
void (*p)(); | |
void (**pp)() noexcept = &p; // 错误:不能转换成指向 noexcept 函数的指针 | |
struct S | |
{ |
1
2
3 1typedef void (*p)();
2operator p();
3
};
void (*q)() noexcept = S(); // 错误:不能转换成指向 noexcept 函数的指针 |(C++17 起) |
1 | 1 |
安全 bool 问题
在 C++11 引入显式转换函数之前,设计一个能用于布尔语境的类(比如, if(obj) { … } )会出现问题:给定用户定义转换函数,如 T::operator bool() const; ,则隐式转换序列允许再多一步标准转换序列,也就是 bool 结果会转换成 int ,允许诸如 obj << 1; 或 int i = obj; 的代码。
一个早期的解决方案可参见 std::basic_ios ,它定义 operator! 和 operator void*(C++11 前),使得如 if(std::cin) {…} 的代码能编译,因为 void* 能转换为 bool ,但int n = std::cout; 不能,因为 void* 不可转换至 int 。这仍然允许无意义代码能编译,如 delete std::cout; 。许多 C++11 前的第三方库设计带有更为复杂的解决方案,称作安全 Bool 手法。
显式 bool 转换亦能用于解决安全 bool 问题 | |
explicit operator bool() const { … } | (C++11 起) |
1 | 1 |
脚注
- ↑ 仅若算术为补码才使用,仅对定宽整数类型要求补码。然而注意目前所有拥有 C++ 编译器的平台都使用补码算术。
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
CWG 616
C++11
任何未初始化对象的左值到右值的转换是未定义行为
允许不定值的 unsigned char
CWG 1423
C++11
std::nullptr_t 在直接或复制初始化中可转换为 bool
只允许直接初始化
CWG 330
C++14
从 double * const (*p)[3] 到 double const * const (*p)[3] 的转换非法
转换合法
5、用户定义转换:允许从类类型到其他类型的隐式转换或显式转换。
语法
转换函数声明类似非静态成员函数或函数模板,而无参数或显式返回类型,并拥有下列形式的名称:
operator conversion-type-id | (1) | |
explicit operator conversion-type-id | (2) | (C++11 起) |
1 | 1 |
-
声明用户定义的转换函数,它参与所有隐式和显式转换
-
声明用户定义的转换函数,它仅参与直接初始化和显式转换。
conversion-type-id 是一个类型 id ,除了函数与数组运算符 [] 或 () 不允许出现于其声明器中(从而转换到诸如指向数组指针的类型要求类型别名/ typedef 或标识模板:见后述)。无关乎 typedef , conversion-type-id 不能表示数组或函数类型。
尽管返回类型不允许出现于用户定义转换函数的声明中,声明文法的 decl-specifier-seq 可以存在并可包含任何异于 type-specifier 或关键词 static 的指定符。尤其是在 explicit 外,亦允许指定符 inline 、 virtual 、 constexpr 及 friend (注意 friend 要求有限定名: friend A::operator B(); )。
类 X 中声明这种成员函数时它进行从 X 到 conversion-type-id 的转换:
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 1struct X {
2 // 隐式转换
3 operator int() const { return 7; }
4
5 // 显式转换
6 explicit operator int*() const { return nullptr; }
7
8// 错误:数组运算符不允许出现于 conversion-type-id 中
9// operator int(*)[3]() const { return nullptr; }
10 using arr_t = int[3];
11 operator arr_t*() const { return nullptr; } // OK 若通过 typedef 进行
12// operator arr_t () const; // 错误:不允许任何情况下转换到数组
13};
14
15int main()
16{
17 X x;
18
19 int n = static_cast<int>(x); // OK :设 n 为 7
20 int m = x; // OK :设 m 为 7
21
22 int* p = static_cast<int*>(x); // OK :设 p 为 null
23// int* q = x; // 错误:无隐式转换
24
25 int (*pa)[3] = x; // OK
26}
27
解释
用户定义的转换函数在隐式转换的第二阶段被调用,第二阶段由零或一个转换构造函数或零或一个用户定义转换函数构成。
若转换函数和转换构造函数都能用于进行用户定义转换,则转换函数和转换构造函数都为复制初始化和引用初始化语境中的重载决议所考虑,但在直接初始化语境中只考虑转换构造函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 1struct To {
2 To() = default;
3 To(const struct From&) {} // 转换构造函数
4};
5
6struct From {
7 operator To() const {return To();} // 转换函数
8};
9
10int main()
11{
12 From f;
13 To t1(f); // 直接初始化:调用构造函数
14// (注意,若转换构造函数不可用,则选择隐式复制构造函数,且调用转换函数以准备其参数)
15 To t2 = f; // 复制初始化:歧义
16// (注意,若转换函数来自非 const 类型,例如 From::operator To();
17// 则它在此情况中替代构造函数被选中)
18 To t3 = static_cast<To>(f); // 直接初始化:调用构造函数
19 const To& r = f; // 引用初始化:歧义
20}
21
到其自身(可有 cv 限定)类(或到其引用),和到类型 void 的转换函数可以定义,但无法作为转换序列的一部分执行,除非在某些情况下通过虚派发:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1struct D;
2struct B {
3 virtual operator D() = 0;
4};
5struct D : B
6{
7 operator D() override { return D(); }
8};
9
10int main()
11{
12 D obj;
13 D obj2 = obj; // 不调用 D::operator D()
14 B& br = obj;
15 D obj3 = br; // 通过虚派发调用 D::operator D()
16}
17
它亦可用成员函数调用语法调用:
1
2
3
4
5
6
7
8
9
10
11
12
13 1struct B {};
2struct X : B {
3 operator B&() { return *this; };
4};
5
6int main()
7{
8 X x;
9 B& b1 = x; // 不调用 X::operatorB&()
10 B& b2 = static_cast<B&>(x); // 不调用 X::operatorB&
11 B& b3 = x.operator B&(); // 调用 X::operatorB&
12}
13
显式调用转换函数时,类型 id 是贪心的:它是作为合法类型 id 的最长可能记号序列(包括属性,若存在):
1
2
3 1& x.operator int * a; // 分析为 & (x.operator int*) a
2 // 而非 & (x.operator int) * a
3
占位符 auto 可用于 conversion-type-id ,指示推导的返回类型: | |
struct X { |
1
2
3
4 1operator int(); // OK
2operator auto() -> short; // 错误:尾随返回类型不是语法的一部分
3operator auto() const { return 10; } // OK :推导的返回类型
4
}; 注意:转换函数模板不允许拥有推导的返回类型。 |(C++14 起) |
1 | 1 |
转换函数可以继承而且可以为虚,但不能为静态。导出类的转换函数不会隐藏基类中的转换函数,除非它们转换到同一类型。
转换函数可以为模板成员函数,例如 std::auto_ptr<T>::operator auto_ptr<Y> 。可应用的特殊规则参阅成员模板和模板实参推导。
6、运行时类型信息
a): std::bad_typeid
定义于头文件 <typeinfo> | ||
class bad_typeid : public std::exception; |
1 | 1 |
此类型的异常在应用 typeid 运算符到多态类型的空指针值时抛出。
继承图
成员函数
(构造函数) | 构造新的 bad_typeid 对象 (公开成员函数) |
1 | 1 |
继承自 std::exception
成员函数
(析构函数) [虚] | 析构该异常对象 (std::exception 的虚公开成员函数) |
what [虚] | 返回解释性字符串 (std::exception 的虚公开成员函数) |
1 | 1 |
运行此代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1#include <iostream>
2#include <typeinfo>
3
4struct S { // 类型必须是多态
5 virtual void f();
6};
7
8int main()
9{
10 S* p = nullptr;
11 try {
12 std::cout << typeid(*p).name() << '\n';
13 } catch(const std::bad_typeid& e) {
14 std::cout << e.what() << '\n';
15 }
16}
17
输出:
1
2 1Attempted a typeid of NULL pointer!
2
b): std::type_info
定义于头文件 <typeinfo> | ||
class type_info; |
1 | 1 |
类 type_info 保有一个类型的实现指定信息,包括类型的名称和比较二个类型相等的方法或相对顺序。这是 typeid 运算符所返回的类。
type_info 既非可复制构造 (CopyConstructible) 亦非可复制赋值 (CopyAssignable) 。
成员函数
(构造函数) [被删除] | 无默认或复制构造函数 (公开成员函数) |
(析构函数) [虚] | 通过指向基类的指针删除导出对象是安全的 (虚公开成员函数) |
operator= [被删除] | 不能复制赋值 (公开成员函数) |
operator== operator!= | 检查对象是否指代相同类型 (公开成员函数) |
before | 检查在实现定义的顺序中,被指代类型是否在另一个 type_index 对象之前,即对被指代类型排序 (公开成员函数) |
hash_code (C++11) | 返回对于同一类型相同的值 (公开成员函数) |
name | 类型的实现定义名称 (公开成员函数) |
1 | 1 |
参阅
type_index (C++11) | 针对 type_info 对象的包装,它能用作关联容器和无序关联容器的索引。 (类) |
1 | 1 |
c): std::bad_cast
定义于头文件 <typeinfo> | ||
class bad_cast : public std::exception; |
1 | 1 |
在 dynamic_cast 对引用类型运行时检查失败(例如因为类型并非以继承关联)时,还有若请求的平面不存在于本地环境时从 std::use_facet 抛出此类型异常。
继承图
成员函数
(构造函数) | 构造新的 bad_cast 对象 (公开成员函数) |
1 | 1 |
继承自 std::exception
成员函数
(析构函数) [虚] | 析构该异常对象 (std::exception 的虚公开成员函数) |
what [虚] | 返回解释性字符串 (std::exception 的虚公开成员函数) |
1 | 1 |
示例
运行此代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1#include <iostream>
2#include <typeinfo>
3
4struct Foo { virtual ~Foo() {} };
5struct Bar { virtual ~Bar() {} };
6
7int main()
8{
9 Bar b;
10 try {
11 Foo& f = dynamic_cast<Foo&>(b);
12 } catch(const std::bad_cast& e)
13 {
14 std::cout << e.what() << '\n';
15 }
16}
17
可能的输出:
1
2 1Bad dynamic cast
2
d): std::type_index
定义于头文件 <typeindex> | ||
class type_index; | (C++11 起) |
1 | 1 |
type_index 类是一个围绕 std::type_info 的包装类,它可用作关联与无序关联容器的索引。它与 type_info 对象的关系通过一个指针维系,故而 type_index 可复制构造 (CopyConstructible) 且可复制赋值 (CopyAssignable) 。
成员函数
(构造函数) | 构造对象 (公开成员函数) [编辑] |
(析构函数) (隐式声明) | 销毁 type_index 对象 (公开成员函数) |
operator= (隐式声明) | 对 type_index 对象赋值 (公开成员函数) |
operator== operator!= operator< operator<= operator> operator>= | 比较底层 std::type_info 对象 (公开成员函数) [编辑] |
hash_code | 返回哈希码 (公开成员函数) [编辑] |
name | 返回关联到底层 type_info 对象的实现定义名称 (公开成员函数) [编辑] |
1 | 1 |
辅助类
std::hashstd::type_index (C++11) | std::type_index 的哈希支持 (类模板特化) [编辑] |
1 | 1 |
下面的程序是一个有效的类型-值映射的示例。
运行此代码
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 1#include <iostream>
2#include <typeinfo>
3#include <typeindex>
4#include <unordered_map>
5#include <string>
6#include <memory>
7
8struct A {
9 virtual ~A() {}
10};
11
12struct B : A {};
13struct C : A {};
14
15int main()
16{
17 std::unordered_map<std::type_index, std::string> type_names;
18
19 type_names[std::type_index(typeid(int))] = "int";
20 type_names[std::type_index(typeid(double))] = "double";
21 type_names[std::type_index(typeid(A))] = "A";
22 type_names[std::type_index(typeid(B))] = "B";
23 type_names[std::type_index(typeid(C))] = "C";
24
25 int i;
26 double d;
27 A a;
28
29 // 注意我们正在存储指向类型 A 的指针
30 std::unique_ptr<A> b(new B);
31 std::unique_ptr<A> c(new C);
32
33 std::cout << "i is " << type_names[std::type_index(typeid(i))] << '\n';
34 std::cout << "d is " << type_names[std::type_index(typeid(d))] << '\n';
35 std::cout << "a is " << type_names[std::type_index(typeid(a))] << '\n';
36 std::cout << "b is " << type_names[std::type_index(typeid(*b))] << '\n';
37 std::cout << "c is " << type_names[std::type_index(typeid(*c))] << '\n';
38}
39
输出:
1
2
3
4
5
6 1i is int
2d is double
3a is A
4b is B
5c is C
6