C++ 强制类型转换
C风格转换
对于内置类型而言,C风格转换就很好用,例如:
int i; double d; i = (int) d; // i = int(d)
然而,这样的转换符不能应用于类(class)和类的指针。
ANSI-C++标准定义了四个新的转换符,用于控制类(class)之间的类型转换:
- reinterpret_cast <new_type> (expression);
- static_cast < type-id > ( expression );
- dynamic_cast<type *>(source *);;
- const_cast<type>(expression);
其中,除了dynamic_cast以外的转换,其行为的都是在编译期就得以确定的,转换是否成功,并不依赖被转换的对象。
const_cast
const_cast 主要是用来去除复合类型中const和volatile属性(没有真正去除)。
变量本身的const属性是不能去除的,要想修改变量的值,一般是去除指针(或引用)的const属性,再进行间接修改。
通过const_cast运算符,也只能将const type*转换为type*,将const type&转换为type&,也就是去掉指向const指针或引用的const特性。
场景1
先看个例子:
const int constant = 26; const int* const_p = &constant; int* modifier = const_cast<int*>(const_p); *modifier = 3; // Undefined Behavior
cout<< "constant: "<<constant<<endl; // 26
cout<< "constant_p: "<<*const_p<<endl; // 3
cout<<"*modifier: "<<*modifier<<endl; // 3
可见,对声明为const的常量来说,它的值并没有改变(const_cast并不是为了改变常量的值而设计的)。
PS:上面这个例子的用法是借助const_cast修改常量,这是一种未定义的行为,不要这样做!
但是对于指向const的指针如果指向一个非const对象,使用const_cast去掉指针的const属性,借以修改其指向的非const对象是ok的,例如:
int main(int argc, char** argv) { int constant = 26; const int* const_p = &constant; int* modifier = const_cast<int*>(const_p); *modifier = 3; return 0; }
场景2
再看一个const_cast应用的例子,如果有一个函数,它的形参是non-const类型变量,而且函数不会对实参的值进行改动,这时我们可以使用类型为const的变量来调用函数,此时const_cast就派上用场了:
void InputInt(int *num) { cout<<*num<<endl; } int main() { const int constant = 21; //InputInt(&constant); // invalid conversion from ‘const int*’ to ‘int*’ InputInt(const_cast<int*>(&constant)); }
场景3
另外一个例子如下,
在const成员函数fun()中的this指针类型为:const student* const this,通过这个this指针无法直接修改数据成员roll;
而const_cast可以将this指针的类型修改为:student* const this,此类型可以修改其所指向的对象;
class student { private: int roll; public: // constructor student(int r):roll(r) {} // A const function that changes roll with the help of const_cast void fun() const { ( const_cast <student*> (this) )->roll = 5; } int getRoll() { return roll; } }; int main(int argc, char** argv) { student s(3); cout << "Old roll number: " << s.getRoll() << endl; s.fun(); cout << "New roll number: " << s.getRoll() << endl; return 0; }
场景4
const_cast还可以用来去除volatile 属性,例如
int main(int argc, char** argv) { int a1 = 40; const volatile int* b1 = &a1; cout << "typeid of b1 " << typeid(b1).name() << ' '; int* c1 = const_cast <int *> (b1); cout << "typeid of c1 " << typeid(c1).name() << ' '; return 0; }
输出:
typeid of b1 PVKi // pointer to a volatile and constant integer typeid of c1 Pi // Pointer to integer
最后,总结下上面列出了几个使用const_cast的场景:
- 指向const的指针修改其所指向的非const对象;
- 将一个const实参传给一个普通形参;
- const成员函数修改非const成员;
- 去volatile属性;
一般转换分为两种: 上行转换和下行转换。
上行转换大致意思是把子类实例向上转换为父类型, 下行转换是把父类实例转换为子类实例;
通常子类因为继承关系会包含父类的所有属性, 但是有些子类的属性父类没有;
所以上行转换的时候,子类实例转换给父类是安全的,转换后的指针或者对象可以放心使用父类的所有方法或者属性;
但是下行转换的时候可能是不安全的,因为假如子类有父类没有的属性或者方法的话,父类指针或者实例转换为子类型后,转换后的实例中并没有子类多出来的方法或属性,当调用到这些方法或属性时程序就会崩溃了。
static_cast(静态转换)
编译时的类型转换,例如:
int main(int argc, char** argv) { float f = 3.5; int a = f; // this is how you do in C int b = static_cast<int>(f); cout << a << endl; // 3 cout << b << endl; // 3 return 0; }
static_cast会检查类型转换是否合法:
int a = 10; char c = 'a'; int* q = (int*)&c; // pass at compile time, may fail at run time int* p = static_cast<int*>(&c); // error!
支持子类指针到父类指针的转换,并根据实际情况调整指针的值,反过来也支持,但会给出编译警告,它作用最类似C风格的“强制转换”,一般来说可认为它是安全的。
class Base { public: Base(string name) {this->name = name;} void f(int p) { cout << "Base::f(int)" << endl; } void f(float p) { cout << "Base::f(float)" << endl; } virtual void g() { cout << "Base::g()" << endl; } virtual void h() { cout << "Base::h() " << name << endl; } private: string name; }; class Derived: public Base { public: Derived(string bname, string name): Base(bname) {this->name = name;} void f(int p) { cout << "Derived::f(int)" << endl; } virtual void f(float p) { cout << "Derived::f(float)" << endl; } void g() { cout << "Derived::g()" << endl; } void h1() { cout << "Derived::h1() " << name << endl; } private: string name; }; int main(int argc, char** argv) { Base b("base"); Derived d("inherit", "derived"); Base *pbb = &b; // ok Base *pbd = &d; // ok Base *pbd2 = static_cast<Base*>(&d); // ok pbd2->f(3); // Base::f(int) pbd2->f(3.14f); // Base::f(float) pbd2->g(); // Derived::g() pbd2->h(); // Base::h() inherit //pbd2->h1(); // error, no member named 'h1' in 'Base' Derived *pdd = &d; // ok //Derived *pdb = &b; // error Derived *pdb = static_cast<Derived*>(&b); // unsafe Derived *pdb1 = dynamic_cast<Derived*>(&b); // ok, but return NULL cout << pdb1 << endl; // 0x00 return 0; }
static_cast主要在以下几种场合中使用:
- 用于类层次结构中,父类和子类之间指针和引用的转换;进行上行转换,把子类对象的指针/引用转换为父类指针/引用,这种转换是安全的;进行下行转换,把父类对象的指针/引用转换成子类指针/引用,这种转换是不安全的,需要编写程序时来确认;
- 用于基本数据类型之间的转换,例如把int转char,int转enum等,需要编写程序时来确认安全性;
- 把void指针转换成目标类型的指针(这是极其不安全的);
dynamic_cast(动态转换)
支持子类指针到父类指针的转换,并根据实际情况调整指针的值,但反过来它就不支持,会导致编译错误,这种转换是最安全的转换;
dynamic_cast 通常用作向下转换【基类到派生类】和平行转换【基类到基类】,运行时,程序会检查source是不是与type类型相兼容,如果是,表达式就返回一个派生类地址,然后转换为type*类型,否则则返回一个NULL。
首先,dynamic_cast依赖于RTTI信息,其次,在转换时,dynamic_cast会检查转换的source对象是否真的可以转换成target类型,这种检查不是语法上的,而是真实情况的检查。
先看RTTI相关部分,通常,许多编译器都是通过vtable(虚函数表)找到对象的RTTI信息的,这也就意味着,如果基类没有虚方法,也就无法判断一个基类指针所指对象的真实类型,这时候,dynamic_cast只能用来做安全的转换,例如从派生类指针转换成基类指针。而这种转换其实并不需要dynamic_cast参与。
也就是说,dynamic_cast是根据RTTI记载的信息来判断类型转换是否合法的。
reinterpret_cast
支持任何转换,但仅仅是如它的名字所描述的那样“重解释”而已,不会对指针的值进行任何调整,用它完全可以做到“指鹿为马”,但很明显,它是最不安全的转换,使用它的时候,你得头脑清醒,知道自己在干什么;