类型转换
在类型转换中,有以下几个简单的原则:
相同的类型之间可以任意转换
空指针可以转变为目的类型的空指针
源类型的 cv 限定 ≤ 目的类型的 cv 限定
为了方便撰写,本文中做出以下规定:
下面假设源类型为 v ,目的类型为 T。例如 const_cast<T>(v)
T1::*data_x代表指向 T.x 的数据成员指针,T::*fn_x代表指向 T.x() 成员函数指针,T::*mem_x代表指向 T.x 或 T.x() 的指针
备注
当初学者刚学到类型转换的时候,可能会迫不及待地将其用于向下类型转换(这里指针指向的对象实际上是基类对象)或者横向类型转换,那么我现在告诉你。C++ 中从来不鼓励也不会教导你这么做,你使用 dynamic_cast 得到的只是一个空指针罢了,使用 reinterpret_cast 是一个未定义行为,使用 static_cast 还不清楚,但是所有的方法都不鼓励你这么做。更可怕的是,如果你不加校验地使用了得到的指针,除非你调用的函数涉及到了类的成员变量,否则函数是可以被调用成功而的。
也就是说: 空指针可以调用不涉及成员变量读写的成员函数
const_cast 1
表达式 const_cast<T>(v) 的结果是类型 T
若 T 是一个左值引用,则结果是一个左值
若 T 是一个对象类型的右值引用,则结果是一个将亡值
否则,使用 标准转换 (左值到右值、数组到指针、函数到指针)产生一个纯右值
所有能够显式使用 const_cast 的表达式如下:
在服从下述条款的情况下,允许相同类型之间的转换
对于 相似类型 T1 和 T2。在考虑 cv 分解的情况下,纯右值 T1 可以转换到类型 T2。表达式的结果取决于源类型
typedef int *A[3]; // int* 数组,大小为 3 typedef int const *const CA[3]; // const int 指针的数组 CA &&r = A{}; // 经过 cv 转换后将临时数组对象绑定到引用 A &&r1 = const_cast<A>(CA{}); // 不允许!临时数组退化到指针 A &&r2 = const_cast<A&&>(CA{}); // 允许
对于对象类型 T1 和 T2 。若 T1* 可以通过 const_cast 显式转换到 T2* ,则: - 通过 const_cast<T2&> 允许 T1 的左值引用转换到 T2 的左值引用 - 通过 const_cast<T2&&> 允许 T1 的泛左值转换到 T2 的将亡值。而且若 T1 是一个类,则允许通过 const_cast<T2&&> 将 T1 的纯右值转换到 T2 的将亡值
如果 v 是一个泛左值,则由 const_cast 产生的引用指向原始对象。否则将会使用 临时实体化转换
如果使用 const_cast 修改了指针、左值或数据成员指针的 cv 限定,那么对其施加写操作,依赖于对象的类型,可能会产生为定义行为
如果 T1 和 T2 不同,从类型 T1 向类型 T2 的变易性的改变将会导致 T1 进行 cv 分解 以让 T2 的 cv 分解 产生这种形式:
\[cv_0^2P_0^2cv_1^2P_1^2\cdots cv_{n-1}^2P_{n-1}^2cv_n^2U_2\]但是 资格转换 并不会将 T1 转换为 \(cv_0^2P_0^2cv_1^2P_1^2\cdots cv_{n-1}^2P_{n-1}^2cv_n^2U_1\)
如果纯右值 T1* 向 T2* 的转换过程中丢弃了常量性,那么使用左值引用转换将 T1 的左值引用转换为 T2 的左值引用或使用右值引用转换将 T1 类型的表达式转换为 T2 类型的将亡值也会丢失常量性
一些只涉及 cv 限定的转换无法通过 const_cast 实现。比如指针没法转换到函数,这种情况将会导致未定义行为。由于一些原因,成员函数指针,尤其是将 const 限定的成员函数指针转变为 非 const 限定的成员函数指针是不允许的
reinterpret_cast 2
若 T 是一个左值引用或函数类型的右值引用,则结果为左值
若 T 是一个对象类型的右值引用,则结果为将亡值
否则,使用 标准转换 (左值到右值、数组到指针、函数到指针)产生一个纯右值
所有能够显式使用 reinterpret_cast 的表达式如下:
reinterpret_cast 不会移除常量性。整型、枚举、指针、指向成员的指针可以被显式转换到它本身的类型
通过 reinterpret_cast 在源值上进行映射时产生的结果 也许 和源值不同
指针可以被转换到任何足够容纳它的整数上。其映射函数取决于实现(这是因为机器的底层地址结构是不同的)
std::nullptr_t 可以被转换到合法的整型上,其含义和有效性与将 (void*)0 转换为整型相同
备注
reinterpret_cast 不允许其他类型转换为 std::nullptr_t
整型或枚举类型可以被显式转为指针类型。指针被转到足以容纳它的整型上然后再转到指针上,其结果于源指针相同。指针与整型之间的映射取决于实现。(除了 basic.stc.dynamic.safety 中的情况外,其他情况无法产生一个安全的指针)
一个函数指针可以被转换到另一种类型的函数指针。(通过这种转换过的函数指针调用函数的结果是未定义的)。除了将一个纯右值的 T1* 转为 T2* 再转为 T1* 外,其余用法是未定义的。(详见 指针转换 )
一个对象指针可以被显式地转为另一种类型的对象指针。当纯右值 obj* 转换到 cv obj* 时,其结果为
static_cast<cv T*>(static_cast<cv void*>(v))。备注
将指向对象 T1 的 T1* 转换到 T2* (此处 T2 的对齐要求不能比 T1 更严格)再转换为初始类型,结果与原指针相同
有条件地支持将函数指针转换为对象指针类型,反之亦然。这种转换的含义是实现定义的,除非一个实现支持双向转换,将一种类型的纯右值转换为另一种类型再转回去,可能具有不同的 cv 限定,但与原始指针具有相同的值
空指针可以转为任何类型的空指针值。
备注
类型为 nullptr_t 的空指针常量无法被转换到空指针,整型的空指针常量不一定转换为空指针值
若 T1 和 T2 同为函数类型或对象类型,则纯右值
T1::*mem_x可以被转换为T2::*mem_Y。除了以下情况外,将空成员指针转换为目的类型的空成员指针的行为是未指定的:将纯右值
T::*fn转到一个不同类型的成员函数指针再转为T::*fn将纯右值
T1::*data_X转为T2::*data_Y(T2 的内存对齐不能比 T1 严格)再转为T1::*data_X
若 T1* 可以通过 reinterpret_cast 转为 T2* ,则 T1 类型的泛左值表达式可以转为 T2的引用。结果是
*reinterpret_cast<T2 *>(p)此处 p 指向是 T1 类型的 x 的指针。没有创建临时对象、没有拷贝、没有构造、没有任何转换函数被调用 3
static_cast 4
对于此表达式而言:
若 T 是左值引用或函数类型的右值引用,则结果为左值
若 T 是对象的右值引用,则结果是将亡值
否则,结果是一个纯右值。 static_cast 不会去除 const 限定
类型 B 的左值可以被转换到其子类 D 的引用上, D 的 cv 限定不得比 B 小。若 B 是 D 的虚有基类、或虚有基类的基类、或不存在从 D* 到 B* 的转换表达式,则不合法。B 的将亡值可以被转换到 D 的右值引用上,此时 D 的 cv 限定与 B 相同。如果 B 实际上是 D 的基类的子对象,则结果引用了 D 类型的嵌套对象。否则结果是未定义的。
例如:
struct B {}; struct D : public B {}; D d; B &br = d; static_cast<D&>(br); // 产生最初 d 对象的左值
若 T2 和 T1 是 引用容许 的,则 T1 的左值可以转到 T2 的右值引用上。如果值不是一个位域,则结果会引用对象或指定的基类子对象,否则,位域将被执行一个 左值到右值 转换,产生的纯右值被用于本节剩余部分的 static_cast 表达式。 若 T2 是 T1 的不可访问或无法确定的基类,则这种转换时不允许的。
如果存在从表达式 e 到 T 的 隐式转换序列 、重载了对象的直接初始化或存在从 e 到类型 T 的引用类型,则表达式 e 可以被显式转换为类型 T 。若 T 是一个引用类型,则此行为等同于声明并初始化
T t(e)。对一些临时变量 t 并使用此临时变量作为转换的结果。否则,此结果对象直接从 e 进行初始化备注
当尝试从类表达式转换到类不可访问或含糊的基类时,此行为不合法
否则,static_cast 将会执行以下行为之一。其他形式的类型转换将不会显式使用 static_cast
任何表达式都可被转换到 cv 限定的 void 类型上,这种情况下表达式将会成为一个 无登记值表达式 .. note:
然而,如果这个值时临时对象,那么它的析构函数直至 Usual Time 时才会运行。对象的值将会被保留以用作析构
标准转换 的任何反转形式不包含以下转换:左值到右值、数组到指针、函数到指针或空指针或空成员指针或布尔或函数指针的转换。正确形式的转换可以显式使用 static_cast 转换。如果程序使用了 static_cast 进行了非法形式的转换,那末程序也是非法的
struct B { }; struct D : private B { }; void f() { static_cast<D*>((B*)0); // error: B is a private base of D static_cast<int B::*>((int D::*)0); // error: B is a private base of D }
如果 static_cast 的转换没有丢失常量性,那么左值到右值、数值到指针和函数到指针的转换可用。以下是对特定部分的额外规则:
域化枚举 可以被显式转换为整型或浮点类型。其结果相当于枚举的底层类型向目的类型的转换。
整型和枚举类型可以被显式转换为一个类型完全的枚举类型。如果枚举具有固定的底层类型,则值先被转换为整型,必要的时候再被转换为枚举类型。如果枚举没有固定类型,而且值在枚举范围之类,则不做任何改变,否则是为定义行为。浮点类型于此类似,先被转换为枚举的底层类型,再转换为枚举
如果 D 是一个类型完全的、派生自 B 的类。那么允许纯右值 cv1 B* 转换为指向纯右值 cv2 D*,这里要求 cv1 不得大于 cv2 。如果 B 是一个虚有基类,或 B 拥有虚有基类,或没有一个从 D* 到 B* 的有效转换,则程序是非法的。允许空指针转换到目的类型的空指针。如果纯右值 cv1 B* 指向的 B 确实是是 D 的一个子类,则产生的目的指针指向 D 类型的闭包对象。否则行为未定义。
如果 D 是 B 的一个类型完全的子类,那么允许 cv1 D::* 到 cv2 B::* 的转换,这里 cv1 ≤ cv2
备注
函数类型(包括指向成员函数的指针)是没有 cv 限定的
如果没有从 B::* 到 T::* 的有效标准转换,则程序非法。空成员指针值可以被转换为目的类型。如果 B 包含成员、是成员变量的基类或是有成员变量的类的派生类,则转换允许。否则行为未定义
这里的 D::* 指的是指向成员变量的指针
备注
虽然B 不需要包含原始成员,但通过成员指针指向的实际对象必须包含原始成员
允许 cv void* 到 cv2 T* 的转换,这里 cv1 ≤ cv2 。如果源指针指向的是 A 的地址,而且 A 不是内存对齐的,则产生的值 未指定 。如果源指针指向的对象 a ,而类型 T(忽视 cv 限定) 的对象 b 是与 a 指针兼容 的,那么产生的结果是指向 b 的指针,否则,指针的值在转换过程中不会发生变化
T* p1 = new T; const T* p2 = static_cast<const T*>(static_cast<void*>(p1)); bool b = p1 == p2; // b will have the value true.
dynamic_cast 5
dynamic_cast 用于将表达式 v 转换为类型 T,这里 T 是一个 类型完全 的类的指针或引用,或者是 cv 限定的 void*。dynamic_cast 不会移除 const 限定。
如果 T 是指针类型, v 是类型完全的类的指针的纯右值,结果产生了一个 T 类型的纯右值
如果 T 是一个左值引用, v 是类型完全的类的左值引用,结果是产生了一个 T 类型的左值引用
如果 T 是一个右值引用, v 是类型完全的类的泛右值,结果是产生了一个 T 类型的将亡值
若 v 与 T 类型相同,且 v 的 cv 限定不低于 T,则结果为 v
如果 v 是一个空指针,结果是产生一个 T 类型的空指针
如果 T 是 B*, v 是 D*,且 B 是 D 的基类,则将 v 所指向的 D 对象的子对象转为唯一一个 B 类型的对象。类似地,如果 T 是一个 B 类型的引用, v 是 D 类型的引用,产生的是唯一引用。不管是指针还是对象,要求 B* 的 cv 限定不得小于 D*,而且 B 不得是 D 的不可访问或混淆基类。
例如:
struct B { }; struct D : B { }; void foo(D* dp) { B* bp = dynamic_cast<B*>(dp); // equivalent to B* bp = dp; }
否则, v 将会成为一个泛左值或多态类型
如果 T 是一个 cv 限定的 void* ,则结果是产生一个 v 所指向的 最亲近对象 的指针。否则,将会在运行时检查被 v 说引用/指向 的对象是否可以被转换到类型 T
如果 C 代表 T 所指向的类,理论上运行时检查方式为:
如果 v 指向 C 对象的公有基类的子对象,而且只有一个 C 类型的对象派生自 v 所指向的子对象,结果指向 C 对象
否则,若 v 指向公有基类的派生类型的对象,而且这个派生类型只有 C 是它的基类,结果指向 C 派生类型的对象。
否则,运行时检查失败
如果转换失败,则得到一个目的类型的空指针。如果是引用转换失败,则抛出一个相关的异常 std::bad_cast
例如:
class A { virtual void f(); }; class B { virtual void g(); }; class D : public virtual A, private B { }; void g() { D d; B* bp = (B*)&d; // cast needed to break protection A* ap = &d; // public derivation, no cast needed D& dr = dynamic_cast<D&>(*bp); // fails ap = dynamic_cast<A*>(bp); // fails bp = dynamic_cast<B*>(ap); // fails ap = dynamic_cast<A*>(&d); // succeeds bp = dynamic_cast<B*>(&d); // ill-formed (not a runtime check) } class E : public D, public B { }; class F : public E, public D { }; void h() { F f; A* ap = &f; // succeeds: finds unique A D* dp = dynamic_cast<D*>(ap); // fails: yields null; f has two D subobjects E* ep = (E*)ap; // ill-formed: cast from virtual base E* ep1 = dynamic_cast<E*>(ap); // succeeds }
备注
dynamic_cast 的转换似乎涉及到了 vtbl,因此只有具有多态的类才能使用 dynamic_cast。这也进一步证明了 dynamic_cast 是一个运行时特性。