the differences among static_cast, reinterpret_cast, const_cast, and dynamic_cast

为什么需要这么多cast

在C中只有一个cast,但是它做了很多种转换

  1. 两个算术类型转换
  2. 指针类型和整型
  3. 两种指针类型
  4. const类型和非const
  5. (4)和(1)(2)(3)中任意一个的组合

并且C++加入了 继承和模板

继承

指针类型转换时,class D 继承 class B,那么(D*)pb应该怎么办?pbB*

有三种可能

  1. 直接返回指向同样byte内存的指针,仅仅转换指针类型(这和C里直接括号转换指针类型是一样的)
  2. 检查是否B*指向的区域真的是D的一部分,如果是就返回D指针,否则失败(可能返回空指针或抛出异常)
  3. 假设B*指向的B是一部分D,不做任何检查,直接指向D,必要的话调整指针地址,

可以想象,在 C++ 中,我们可能希望都能执行这三种转换。所以C++给出了三种cast

  • (1) 对应 reinterpret_cast<D*>
  • (2) 对应 dynamic_cast<D*>
  • (3) 对应 static_cast<D*>

模板

如果在C++中使用C风格的cast,并且转换的源类型或目标类型或者都是模板参数或依赖于模板参数,那么通常涉及的转换类型可能取决于模板参数。
例如

1
2
3
4
template <class T> 
unsigned char* alias(T& x) {
return (unsigned char*)(&x);
}

这里可以传任意左值,得到指向它的第一个byte的指针。

但是!
如果我传了一个const value会发生什么呢?

C风格的转换会悄悄滴把const去掉,然后返回的这个指针指向的x的byte可以被修改了,后面就是undefined behavior。

我们发现,C风格的转换到指针有时会改变指向对象的类型,有时又移除了const,有时既改变类型又移除const。

那么写成这样好一点

1
2
3
4
template <class T> 
unsigned char* safe_alias(T& x) {
return reinterpret_cast<unsigned char*>(&x);
}

这样,如果传进来的是const左值,这里就有编译错误来告诉你“哦!你把const转没了,这超危险的,好在我及时发现了呢”

这就是为什么我们需要拎出来一个const_cast。这个函数是和其他三个完全不一样的,你几乎很少需要用到把const移走,并且大多数其实是希望编译器阻止你不小心把const移走。如果每个static_cast,dynamic_cast,reinterpret_cast都可以同时移走const,那么用起来就太危险了,尤其是用模板的时候。现在如果谁真的想拿到const intchar*那么可以使用safe_alias(const_cast<int&>(x)),这样就是显式的移除const。

C++ 有模板的事实有时也迫使我们显式地执行隐式转换!在C中:

1
2
3
4
5
6
int* pi; 
void* pv = pi; // OK; implicit conversion
void* pv2 = (void*)pi; // OK but unnecessary
void f(void* pv);
f(pi); // OK; implicit conversion
f((void*)pi); // OK but unnecessary

而在c++中就可以这样写

1
2
3
4
5
template <class T> void f(T* p); 
// ...
int* pi;
f(pi); // OK; calls f<int>
f(static_cast<void*>(pi)); // OK; calls f<void>

虽然int*可以转成void*而不用任何转换,但是我还是要这样做来强制保证有一个正确的类型。(这是因为想要调用的可能是f<void>而不是f<int>

Boost中有implicit_cast模板函数,用来显式的进行隐式转换,很多人认为应该加入到C++标准。

Conclusion

  • static_cast 进行隐式转换、隐式标准转换的逆过程,以及(可能不安全的)基到派生的转换
  • reinterpret_cast 在不更改地址的情况下将一个指针转换为另一个指针,或在指针与其数字(整数)值之间进行转换。
  • dynamic_cast 仅向上和向下转换类层次结构,始终检查请求的转换是否有效。
  • const_cast 只改变const,所有其它cast都不能改变其是否const

C++中有四种不同类型转换的原因是可以写一个类型转换并明确说明打算执行哪种类型的转换; 编译器永远不会错误地“猜测”你的意思; 其他阅读你的代码的人也能够分辨出它进行了何种转换。