C++强制类型转换(dynamic_cast,static_cast, const_cast, reinterpret_cast)
概述
C++同时提供了4种新的强制类型转换形式(通常称为新风格的或C++风格的强制转 型):const_cast(expression)、dynamic_cast(expression)、 reinterpret_cast(expression)和 static_cast(expression),每一种都适用于特定的目的。
dynamic_cast
沿继承层级向上、向下及侧向转换到类的指针和引用。
语法
dynamic_cast < new_type > ( expression )
- new_type - 指向完整类类型的指针、到完整类类型的引用,或指向(可选的 cv 限定) void 的指针
- expression - 若 new_type 为引用,则为完整类类型的左值 (C++11 前)泛左值 (C++11 起)表达式,若 new_type 为指针,则为指向完整类类型的指针纯右值。
若转型成功,则 dynamic_cast 返回 new_type 类型的值。
若转型失败且 new_type 是指针类型,则它返回该类型的空指针。若转型失败且 new_type 是引用类型,则它抛出匹配类型 std::bad_cast 处理块的异常。
注意:dynamic_cast在将父类cast到子类时。父类必需要有虚函数。比如在以下的代码中将CBasic类中的test函数不定义成
virtual时,编译器会报错:error C2683: dynamic_cast : “CBasic”不是多态类型
对编译器的要求:
dynamic_cast<> 会用到RTTI技术。因此须要启动“执行时类型信息”这一选项,而在VC.net 2003中默认是关闭的。
dynamic_cast范例
#include <iostream>
using namespace std;
class CBasic
{
public:
virtual int test(){return 0;} // 一定要是 virtual
};
class CDerived : public CBasic
{
public:
virtual int test(){ return 1;}
};
int main()
{
CBasic cBasic;
CDerived cDerived;
CBasic * pB1 = new CBasic;
CBasic * pB2 = new CDerived;
//dynamic cast failed, so pD1 is null.
CDerived * pD1 = dynamic_cast<CDerived * > (pB1);
std::cout << "pD1 = " << pD1 << std::endl;
//dynamic cast succeeded, so pD2 points to CDerived object
CDerived * pD2 = dynamic_cast<CDerived * > (pB2);
std::cout << "pD2 = " << pD2 << std::endl;
//dynamci cast failed, so throw an exception.
// CDerived & rD1 = dynamic_cast<CDerived &> (*pB1);
//dynamic cast succeeded, so rD2 references to CDerived object.
CDerived & rD2 = dynamic_cast<CDerived &> (*pB2);
return 0;
}
static_cast
用隐式和用户定义转换的组合在类型间转换。
语法
static_cast < new_type > ( expression )
- 返回 new_type 类型的值。
static_cast 可以被用于强制隐形转换(例如,non-const对象转换为const对象,int转型为double,等等),它还可以用于很多这样的转换的反向转换 (例如,void*指针转型为有类型指针,基类指针转型为派生类指针),但是它不能将一个const对象转型为non-const对象(只有 const_cast能做到),它最接近于C-style的转换。应用到类的指针上,意思是说它允许子类类型的指针转换为父类类型的指针(这是一个有效的隐式转换),同时,也能够执行相反动作:转换父类为它的子类。
static_cast范例
#include <vector>
#include <iostream>
struct B {
int m = 0;
void hello() const {
std::cout << "Hello world, this is B!\n";
}
};
struct D : B {
void hello() const {
std::cout << "Hello world, this is D!\n";
}
};
enum class E { ONE = 1, TWO, THREE };
enum EU { ONE = 1, TWO, THREE };
int main()
{
// 1: 初始化转换
int n = static_cast<int>(3.14);
std::cout << "n = " << n << '\n';
std::vector<int> v = static_cast<std::vector<int>>(10);
std::cout << "v.size() = " << v.size() << '\n';
// 2: 静态向下转型
D d;
B& br = d; // 通过隐式转换向上转型
br.hello();
D& another_d = static_cast<D&>(br); // 向下转型
another_d.hello();
// 3: 左值到右值
std::vector<int> v2 = static_cast<std::vector<int>&&>(v);
std::cout << "after move, v.size() = " << v.size() << '\n';
// 4: 弃值表达式
static_cast<void>(v2.size());
// 5. 隐式转换的逆
void* nv = &n;
int* ni = static_cast<int*>(nv);
std::cout << "*ni = " << *ni << '\n';
// 6. 数组到指针后随向上转型
D a[10];
B* dp = static_cast<B*>(a);
// 7. 有作用域枚举到 int 或 float
E e = E::ONE;
int one = static_cast<int>(e);
std::cout << one << '\n';
// 8. int 到枚举,枚举到另一枚举
E e2 = static_cast<E>(one);
EU eu = static_cast<EU>(e2);
// 9. 指向成员指针向上转型
int D::*pm = &D::m;
std::cout << br.*static_cast<int B::*>(pm) << '\n';
// 10. void* 到任何类型
void* voidp = &e;
std::vector<int>* p = static_cast<std::vector<int>*>(voidp);
}
运行结果
n = 3
v.size() = 10
Hello world, this is B!
Hello world, this is D!
after move, v.size() = 0
*ni = 3
1
0
const_cast
const_cast一般用于强制消除对象的常量性。它是唯一能做到这一点的C++风格的强制转型。这个转换能剥离一个对象的const属性,也就是说允许你对常量进行修改。
语法
const_cast < new_type > ( expression )
- 返回 new_type 类型的值。
const_cast范例
#include <iostream>
struct type {
int i;
type(): i(3) {}
void f(int v) const
{
// this->i = v; // 编译错误: this 是指向 const 的指针
const_cast<type*>(this)->i = v; // 只要该对象不是 const 就 OK
}
};
class C
{
};
int main()
{
int i = 3; // 不声明 i 为 const
const int& rci = i;
const_cast<int&>(rci) = 4; // OK :修改 i
std::cout << "i = " << i << '\n';
type t; // 假如这是 const type t ,则 t.f(4) 会是未定义行为
t.f(4);
std::cout << "type::i = " << t.i << '\n';
const int j = 3; // 声明 j 为 const
int* pj = const_cast<int*>(&j);
// *pj = 4; // 未定义行为
void (type::* pmf)(int) const = &type::f; // 指向成员函数的指针
// const_cast<void(type::*)(int)>(pmf); // 编译错误: const_cast 不在成员函数指针上工作
const C *a = new C;
C *b = const_cast<C *>(a);
}
运行结果
i = 4
type::i = 4
reinterpret_cast
与 static_cast 不同,但与 const_cast 类似, reinterpret_cast 表达式不会编译成任何 CPU 指令(除非在整数和指针间转换,或在指针表示依赖其类型的不明架构上)。它纯粹是一个编译时指令,指示编译器将 expression 的位序列(对象表示)视为 new_type 类型的位序列。
语法
reinterpret_cast < new_type > ( expression )
- 返回 new_type 类型的值。
reinterpret_cast 是特意用于底层的强制转型,导致实现依赖(就是说,不可移植)的结果,例如,将一个指针转型为一个整数。这样的强制类型在底层代码以外应该极为罕见。操作 结果只是简单的从一个指针到别的指针的值得二进制拷贝。在类型之间指向的内容不做任何类型的检查和转换。
reinterpret_cast范例
#include <cstdint>
#include <cassert>
#include <iostream>
int f() { return 42; }
int main()
{
int i = 7;
// 指针到整数并转回
std::uintptr_t v1 = reinterpret_cast<std::uintptr_t>(&i); // static_cast 为错误
std::cout << "The value of &i is 0x" << std::hex << v1 << '\n';
int* p1 = reinterpret_cast<int*>(v1);
assert(p1 == &i);
// 到另一函数指针并转回
void(*fp1)() = reinterpret_cast<void(*)()>(f);
// fp1(); 未定义行为
int(*fp2)() = reinterpret_cast<int(*)()>(fp1);
std::cout << std::dec << fp2() << '\n'; // 安全
// 通过指针的类型别名使用
char* p2 = reinterpret_cast<char*>(&i);
if(p2[0] == '\x7')
std::cout << "This system is little-endian\n";
else
std::cout << "This system is big-endian\n";
// 通过引用的类型别名使用
reinterpret_cast<unsigned int&>(i) = 42;
std::cout << i << '\n';
const int &const_iref = i;
// int &iref = reinterpret_cast<int&>(const_iref); // 编译错误——不能去除 const
// 必须用 const_cast 代替: int &iref = const_cast<int&>(const_iref);
}
运行结果
The value of &i is 0x61fe84
42
This system is little-endian
42
dynamic_cast和static_cast的区别
在C++中,dynamic_cast和static_cast都是用来转型的操作符,两者不合理的运用可能会导致在编译期合法的类型转换操作却在运行期也会引发错误,当转型操作涉及到对象指针或引用时,更易发生错误。 这两者又有什么区别呢?
- dynamic_cast操作符会在运行期对可疑的转型操作进行安全检查,而static_cast操作符不会进行安全检查;
- dynamic_cast仅对多态有效(转型的源类型必须是多态,但与转型的目标类型是否多态无关),而static_cast可施加与任何类型;
- 从派生类到基类的 dynamic_cast 可以进行,这称为向上转型;
- 从基类到派生类的 dynamic_cast 不能进行,称为向下转型;
- 有继承关系,派生类可通过dynamic_cast向基类转换;
- 没有继承关系,不能通过dynamic_cast互换;
使用方式:
dynamic_cast<T*>ptr、static_cast<T*>ptr;
dynamic_cast<T&>p、static_cast<T*>p;
下面用一些简单的代码来说明关于转型的一些知识点:
一个基类指针不经过明确的转型操作,就能指向基类对象或派生类对象;反过来,一个派生类指针指向基类对象是一种不明智的做法。
class B
{
...
};
class D : public B
{
...
};
int main( )
{
D* p;
p = new B(); // error
p = static_cast<D*>(new B()); // 合法,但可能会造成难以跟踪的运行错误
}
再看下面一段代码:
#include <iostream>
using namespace std;
class B
{
public:
virtual void f() { cout<< "f()" <<endl; }
};
class D : public B
{
public:
void m(){cout<< "m()" <<endl;}
};
int main()
{
D* p = static_cast<D*>(new B);
p -> m(); // ...
return 0;
}
其中p->m()编译能通过,有些编译器运行出错。因为P实际指向一个B的对象,而B没有成员函数m,这种转型不安全(在MinGW gcc-6.3.0中,可正确运行,输出:m())。
C++提供的dynamic_cast操作符可以在运行期检测某个转型动作是否安全。dynamic_cast和static_cast有同样的语法,不过dynamic_cast仅对多态类型有效。
#include <iostream>
using namespace std;
class C
{
// C类中无虚函数
};
class T : public C
{
};
int main( )
{
T* p = dynamic_cast<T*>(new C()); // error,仅对多态类型有效, 换成static_cast编译通过
return 0;
}
注意:dynamic_cast操作正确的前提是——转型的源类型必须是多态的, 但与转型的目标类型是否多态无关。 在<>中指定的dynamic_cast的目的类型必须是一个指针或引用。
看一个正确的用法:
#include <iostream>
using namespace std;
class B
{
public:
virtual void f() {cout << "f()" << endl;}
};
class D:public B
{
public:
void m() {cout << "m()" << endl;}
};
int main()
{
D* p = dynamic_cast<D*>(new B()); // 能够判断转型是否安全,如果安全,则返回B对象的地址,否则返回NULL。本例返回NULL。
B* b_p = new D();
cout << "dynamic_cast1 \n";
if (p)
{
p -> m();
}
else
{
cout << "Error\n";
}
cout << "dynamic_cast2 \n";
p = dynamic_cast<D*>(b_p) ;
if (p)
{
p -> m();
}
else
{
cout << "Error\n";
}
return 0;
}
参考
[1] dynamic_cast和static_cast的区别
[2] static_cast ,reinterpret_cast - 夏雪冬日