C++是一种强类型的编程语言,为了便于在不同类型之间相互转换,C++提供了四种类型转换操作符:dynamic_cast, reinterpret_cast, static_cast, const_cast。
C-style类型转换
(new_type) expression 或 new_type(expression)
对于简单类型来说,这种转换可以很好地工作,例如:
double d = 3.14;
int i = int(d); // i == 3
double d2 = (double)i; // d2 == 3.0
但如果涉及到类和指针的类型转换,这种方式往往不能正确地工作,甚至会导致不可预知的错误,例如:
#include<iostream>
using namespace std;
class A
{
double a;
public:
A(double _a) :a(_a) {}
double get()
{
return a;
}
};
class B
{
int b;
public:
B(int _b) :b(_b) {}
};
int main()
{
B b(3);
A *a = (A*)&b;
cout << a->get() << endl;
return 0;
}
上述代码企图将B类型的变量转换成A类型的变量,并访问变量的成员函数,这无疑会导致运行时无法预知的结果,但编译器无法在编译期间报告这样的错误,因此这种类型转换容易导致难以发现的错误。
dynamic_cast
dynamic_cast<new_type>(expression)
dynamic_cast只能用于对象的指针和引用,可以确保类型转换之后的目标指针指向有效的对象。
在一个继承体系中,dynamic_cast显然可以从子类向父类转换。但dynamic_cast同时还可以做安全的向下类型转换(仅限于带有虚函数的类型),即如果一个父类的指针指向子类的类型,dynamic_cast可以将父类的指针强制转换成子类的指针。
#include<iostream>
using namespace std;
class Base
{virtual void fun() {} };
class Derived: public Base {};
class DDerived: public Derived {};
int main()
{
Base *pb = new Derived;
Derived *pd;
DDerived *pdd;
pd = dynamic_cast<Derived*>(pb); // ok
cout << "From pb to pd: " << (pd ? "ok.\n": "error.\n");
pdd = dynamic_cast<DDerived*>(pb); // error
cout << "From pb to pdd: " << (pdd ? "ok.\n": "error.\n");
return 0;
}
上述代码企图将Derived类型的变量强制转换成DDerived类型,dynamic_cast返回一个空指针表明pb指向的对象不是一个完整的DDerived类型(但该对象是一个完整的Derived类型)。 如果dynamic_cast被用来强制转换类型的引用,当转换失败时会抛出bad_cast异常。
注意:dynamic_cast需要运行时类型信息(RTTI)来确定类型转换是否可行,需要编译器支持才能正常工作。并且dynamic_cast是有运行时开销的。
dynamic_cast还可以将空指针转换成任意类型的指针,或者将任意类型的指针转换成void*。
static_cast
static_cast<new_type>(expression)
static_cast可以将子类指针转换为父类指针,也可以将父类指针转换为子类指针,且不进行安全性检查,但不能将指针在完全不相关的类型之间转换,例如不能将Base*直接转换为int*。
static_cast还可以执行以下类型转换:
- 基本类型之间的转换;
- 将void*转换为任意指针类型,或将任意指针类型转换为void*;
- 调用类型转换运算符;
- 将变量转换为右值引用;
- 将枚举类型转换为整型或浮点型;
- 将任意类型转换为void类型。
#include <iostream>
#include <vector>
using namespace std;
class C
{
int c;
public:
C(){}
operator int()
{ return c; }
};
enum class E { zero, one, two, three };
int main()
{
// 基本类型之间的转换
int i = static_cast<int>(3.14); // 3
double d = static_cast<double>(i); // 3.0
// void*与任意指针之间的转换
int *pa = new int(0);
void *pv = static_cast<void*>(pa);
double *pd = static_cast<double*>(pv);
delete pa;
// 类型转换运算符
C c;
int j = static_cast<int>(c);
// 转换为右值引用
vector<int> v1({1, 2, 3});
// value in v1 moved to v2
vector<int> v2 = static_cast<vector<int>&&>(v1);
// 枚举类型与整型
int k = static_cast<int>(E::one); // k == 1
// 任意类型转换为void类型
int a, b;
static_cast<void>(a);
return 0;
}
reinterpret_cast
reinterpret_cast<new_type>(expression)
reinterpret_cast用于在任意两种指针之间转换,且不进行任何安全性检查,即使两种指针之间没有任何的继承关系,转换的结果是原指针二进制数值的直接拷贝。同时还可以在整型和指针类型之间进行类型转换。
#include <iostream>
using namespace std;
class A{};
class B{};
int main()
{
A *a = new A;
B *b = reinterpret_cast<B*>(a);
intptr_t p = reinterpret_cast<intptr_t>(b);
}
尽管上述代码可以编译通过,但对b的解引用会造成未知的错误,reinterpret_cast只负责转换,转换的安全性应当由代码的编写者保证。
const_cast
const_cast<new_type>(expression)
const_cast用于增加或去除指针或引用的const属性。
#include <iostream>
using namespace std;
void f(int *pa)
{
cout << *pa << endl;
}
int main()
{
const int a=0;
int *b = const_cast<int*>(&a);
f(b);
return 0;
}
尽管我们可以使用const_cast去除const属性,但对该变量的修改依然是未定义的行为,const_cast的(其中一个)用途为:当函数接口只接受非const指针且函数保证不对数据进行修改,但我们手上只有一个数据的const指针,此时需要const_cast去除const属性以供函数使用。
总结
对于基本数据类型(整型、浮点型、布尔型)之间的转换,使用C类型的类型转换便可以清晰明了地表达真正的意图;对于自定义类型之间的转换,考虑dynamic_cast和static_cast;除非必须,尽量不要使用reinterpret_cast在无关的指针类型之间转换,以防引入不必要的麻烦;尽管const_cast可以去除指针的const属性,对一个const变量的修改依然是一个错误的行为,任何时候都不要这样做。
本文由kedixa发表于个人博客,
转载请注明作者及出处。
本文链接:https://blog.kedixa.top/2017/cpp-four-casts/