C++ 类型转换操作符
C语言的类型转换操作有其局限性,因此C++语言增加了4个类型转换操作符。
- reinterpret_cast
- dynamic_cast
- static_cast
- const_cast
它们的语法都是一致的:
xxxx_cast < type-id > ( expression )
reinterpret_cast
对原始比特位重解释
顾名思义,reinterpret 就是重解释,reinterpret_cast 的作用就是对 expression
所表示的内容(比特位)按 type-id
类型来解释,原始数据没有任何的改变!
unsigned short Hash( void* p )
{
unsigned val = reinterpret_cast<unsigned>( p );
return (unsigned short)(val ^ ( val >> 16 ) );
}
这里p
是一个地址,在32位操作系统下这是一个32比特的数据,正好可以解释成int
, 因此上面reinterpret_cast
这一行作用就是将地址解释成无符号整型。
用法总结
expression | type-id |
---|---|
pointer | pointer |
integral type | pointer |
pointer | integral type |
pointer to pointer 指针到指针的转换
允许无关类型指针的互相转换(指针类型无关性)integral to pointer 整型到指针的转换
整型包括整数,bool, Enum等等都可以转换成指针pointer to integral 指针到整型的转换
需要注意的是这时候指针值有可能是32位,也有可能是64位,这时候目标整型数据需要有足够的空间能够容纳这个值。
注意点
- MSDN文档指出对
reinterpret_cast
所得结果的使用是不安全的,这是指针类型无关性的特性所导致的。 reinterpret_cast
不能祛除const
,volatile
属性。
dynamic_cast
用于多态(polymorphic)相关类型的转换
具有运行时类型检查(Run-time Type Check)的安全特性
dynamic_cast
, 所谓动态转换,意味着运行时类型安全性。 当然安全是要付出代价的,代价就是运行时的类型检查。当dynamic_cast通过运行时类型检查,发现失败时,它会返回空指针或扔出 bad_cast
exception。
用法总结
expression | type-id |
---|---|
pointer | pointer |
左值 | reference |
- 由于
dynamic_cast
适用于多态相关类型的转换,它不能使用于有基本类型或没有虚函数的自定义类型参加的场合。 - 在转换成引用时,
expression
必须是左值,因此不能是临时变量。
简单多态
dynamic_cast
的基本用法就是在类层次结构之间的上下转换。对于这一类的转换,唯一需要注意的是将基类指针转换成子类指针时,要注意检查转换结果,在引用的转换时要注意 catch bad_cast
exception。
多重继承
多重继承时,类层次关系复杂,是使用dynamic_cast的难点。使用dynamic_cast时, 有可能出现多个基类继承自同一个上层基类的情形,考虑如下情形:
E* p = new E;
A* a = p;
此时转换会带来二义性, 因此转换失败。此时我们要做的则是多次转换:
B* b = dynamic_cast<B*>(p);
A* a = dynamic_cast<A*>(b);
当然此时由于都是上行转换,即使不使用任何转换操作符都能够成功转换。
继续!
D* d = p;
如何将指针d转换成指向A的指针?(假设我们需要得到B subobject)。
思路:
首先我们得把d 还原成指向E的指针p,再将p转换成指向B的指针b,最后得到A* a。
E* p = dynamic_cast<E*>(d);
B* b = dynamic_cast<B*>(p);
A* a = dynamic_cast<A*>(b);
这样一个简单操作竟然需要3行代码,其实dynamic_cast
还有一个不为人知的特异功能,那就是交叉转换(cross cast):
B* b = dynamic_cast<B*>(d);
由于dynamic_cast
运行时类型检查的特性,它可以毫不费力的将指针d转换成指向B的指针。也就是说在类层次结构中,只要转换没有二义性,dynamic_cast
可以一次转换完成,所有的转换工作都由背后的运行时类型检查功能完成!但是当出现二义性的时候,则需要程序员的介入,通过多次转换消除二义性!
注意点
dynamic_cast
是作用于多态相关类的转换的操作符,因此不适用于基本类型或无虚函数的类型的转换。- 进行
dynamic_cast
的类型之间必须具有一定相关性,或者说在同一个类层次结构中。 当然二者之间可能并没有直接的继承关系,但可能是由于二者具有共同的子类。另一方面,在同一个类层次结构中的类,通过dynamic_cast
不一定能够转换成功。 - 运行时类型检查确保了转换的安全性,但是一定程度上带来了额外的消耗!
dynamic_cast
不能祛除const
,volatile
属性。- 由于动态运行时类型检查的额外开销会对某些资源有限的嵌入式系统带来一定压力,编译器通常会提供关闭动态类型检查的选项,此时使用dynamic_cast可能会导致编译错误!
static_cast
static_cast
是传统C强制转换操作符的化身,也就是说二者的使用场合一致。
static_cast
的 type-id
可以是任何类型,也就是说type-id
可以是自定义类型,而并不一定是指针/引用。
用法总结
- 继承关系的类型的转换
具有继承关系的类型之间可以使用static_cast
来进行转换,但是static_cast
由于没有运行时类型检查,这种转换可能是不安全的。
class A {};
class B : public A {};
int main()
{
A* a = new A;
B* b = new B;
A* a1 = static_cast<A*> b; //上行转换没问题
B* b1 = static_cast<B*> a; //下行转换编译通过,但是不安全
return 0;
}
- 无关类型转换,编译错误。
class A{};
class B{};
B* b = new B;
A* a = static_cast<A*> (b); //编译错误
int* p = static_cast<int*>(b); //编译错误
- 只关心表达式中
type-id
,expression
的字面类型,不管实际类型。
B* b = dynamic_cast<B*>(d); //cross cast, ok
B* b = static_cast<B*>(d); // 编译错误,B, D不相关,即使d实际指向E
在上面讲解dynamic_cast
的时候,提到dynamic_cast
具有交叉转换的特异功能,显然这是由它运行时类型检查提供的功能。static_cast
并不具有这种能力。
基本类型的转换
对于基本类型的转换,就不多说了。可用于用户自定义类型
class A {};
class B
{
B( const A& );
};
class C
{
operator A ();
};
A a;
B b = static_cast<B>(a); //正确,使用构造函数
B b1;
b1 = static_cast<B>(a); //正确,使用构造函数
C c;
A a1 = static_cast<A>(c); //正确,使用转换成员函数
A a2;
a2 = static_cast<A>(c); //正确,使用转换成员函数
显然对于用户自定义类型,static_cast
是多余的,在没有使用static_cast
的时候,编译器也会自动检查代码中的转换构造函数来完成转化。
我们总结一下static_cast
的用法,对于继承关系的类型,大多数情况下dynamic_cast可能更安全,对于用户自定义类型,编译器会自动完成。唯独,只有基本类型的转换需要用到它,而此时这种转换其实可以使用C的强制转换操作符完成。
由此可见,C++中提供的static_cast
是没有必要的,如果真要说它的用处,那就是它在C++中提供了一个选项,取代了C的强制类型转换操作符。
const_cast
用来祛除变量的
const
,volatile
属性。
用法总结
expression | type-id |
---|---|
const/volatile pointer | pointer |
const/volatile 左值 | reference |
祛除了const
,volatile
属性之后的返回指针(或引用)还是指向原始对象,因此const_cast
只适用于指针或引用。
总结
- 只有
const_cast
可以用来祛除const
,volatile
属性。 - 只有
dynamic_cast
进行运行时类型检查。 dynamic_cast
,const_cast
只能进行指针,引用转换。static_cast
可用于指针,引用的转换。但是也适用于基本类型之间,定义了转换构造/成员函数的自定义类之间的转换。reinterpret_cast
只能作用于指针和整型。它只进行低层次比特位的重新解释,但是不会修改数据。- 当需要在类层次中相互转换时,使用
dynamic_cast
。 - 当需要进行基本类型之间的转换时,使用
static_cast
。 - 当需要对某块内存中的内容重新解释时,可以考虑使用
reinterpret_cast
。 - 如果编译器提供了隐式转换,最好什么转换操作符都不要用!