0%

C++中的几种cast

C++虽然集成了C语言的explict cast和implict cast策略,但是作为一门更现代的语言,C++额外提供了一些类型检查,如检查指针和基础类型的转换,基类和派生类的转换等来提高编程的安全性。
主要有const_cast,reinterpret_cast,static_cast,dynamic_cast。其中const_cast主要用于处理const修饰,功能单一,可以看作一类,其他三类可以看作另一类。

const_cast

const_cast一般用于强制修改一个类型的读写权限,例如实现常量到变量引用的转换,以实现修改一些常量的目的。强制转换的类型必须是指针或引用。

const int v = 1;
int &mutv = std::const_cast<int&>(v);
mutv++;

const int *p = 1;
int *mutp = std::const_cast<int*>(p);
(*mutp)++;

reinterpret_cast

几乎什么都可以转,可以看作是内存层面的转化,是最为接近C风格的强制转换reinterpret_caststatic_cast基础上支持更宽松的类型转换,例如支持指针和基础类型的相互转换。

它的机制是对二进制数据进行重新的解释,不会改变原来的格式,而static_cast则会改变原来的格式。

int main() {
int i = 100;
void *ptr = reinterpret_cast<void*>(i); // int => void*
return 0;
}

由于reinterpret_cast是二进制层面上的转化,不考虑转化后的数据是否有意义,不会主动抛出异常。这类强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错,所以尽可能不要用

static_cast

static_cast是在编译阶段实现的静态映射,用于各类隐式转换,如果转化失败会抛出异常

  • constconst类之间的相互转换
  • 基础类型int,unsigned int,float之间的相互转换,转换符合C的转换规则。
  • 任何指针类之间的相互转换
  • 将任何类型转化为void类型
  • 对于多态,只支持派生类的向基类的单向转化。(如果是指针,可以双向转换,但是不安全。)
  • 如果子类不包含额外成员,也支持用static_cast进行基类到子类的转换
  • 不支持指针和基础类型(int,float)等之间的相互转换
class Base {
public:
Base() { std::cout << "Base construct" << std::endl;}
Base(const Base& base) { std::cout << "Base copy construct" << std::endl;}
};

class Derive: public Base {
public:
Derive() { std::cout << "Derive construct" << std::endl; }
Derive(const Derive& derive) { std::cout << "Derive copy construct" << std::endl;}
};

int main() {
int i = 1;
float f = 3.14;
int* ptr = &i;
const int ci = static_cast<const int>(i);
int ici = static_cast<int>(ci);
std::cout << static_cast<float>(i) << std::endl;
std::cout << static_cast<int>(f) << std::endl;
// std::cout << static_cast<int*>(i) << std::endl; // compile error
// std::cout << static_cast<int>(ptr) << std::endl; // compile error

Base base;
Derive derive;
// Derive derive2 = static_cast<Derive>(base); // compile error
Base base2 = static_cast<Base>(derive);
return 0;
}

输出:

1
3
Base construct
Base construct
Derive construct
Base copy construct

对于上述例子,可以看出多态场景下派生类向基类值的转换涉及到拷贝构造,将会以派生类中基类的数据为参调用基类的拷贝构造函数构造一个新对象。对于基础类型的转化也是基于复制的,会分配新的栈上空间。

Derive derive;
const Base& baseref1 = static_cast<Base&>(derive); // no copy construct happened
Base& basevalue = static_cast<Base&>(derive); // copy construct happened
const Base& baseref2 = static_cast<Base>(derive); // copy construct happened

转化类型是否涉及拷贝取决于转化后的目标是否占用新的栈上空间。如果转化后的值需要占用栈上空间,或者转化目标不是引用或指针,则会发生拷贝构造。如果不想发生拷贝构造,可以使用dynamic_cast

dynamic_cast

dynamic_cast<T*>用于动态类型转换,只能用于含有虚函数的类,用于类层次之间的向上和向下转换,只能转指针或引用(所以转化过程不会设计拷贝构造),如果是非法的对于指针返回NULL,对于引用抛异常dynamic_cast的检测发生在运行时,需要检查虚表判断是否能够向下转换。

最为严格的强制转换,只能进行指针或者引用类型转换,而且要求满足转换安全。即只允许派生类类型向基类类型转换,否则抛出异常

dynamic_cast使用了RTTI(RunTime Type Indentify)来检查类指针/引用的派生关系。RTTI提供了3个主要的操作符:

  • dynamic_cast
  • typeid
  • type_info

当typeid中的操作数是如下情况之一时,typeid运算符指出操作数的静态类型,即编译时的类型。

  1. 类型名
  2. 一个基本类型的变量
  3. 一个具体的对象(非指针对象)
  4. 一个指向不含有virtual函数的类对象的指针的解引用
  5. 一个指向不含有virtual函数的类对象的引用

静态类型在程序的运行过程中并不会改变,所以并不需要在程序运行时计算类型,在编译时就能根据操作数的静态类型,推导出其类型信息。例如如下的代码片断,typeid中的操作数均为静态类型:

dynamic_pointer_cast

lexical_cast

参考资料

Disqus评论区没有正常加载,请使用科学上网