C语言中的强制类型转换通常不安全,根据强转的类型造成变量内存扩大或缩小,访问不安全
- const_cast : 去掉const属性的一个类型转换,只能操作指针或引用类型,转换前后指针访问的大小不一样,就禁止转换
- static_cast : 提供编译器认为安全的类型转换(没有任何联系的类型之间的转换无法通过编译),可以将
Derive
转为Base
(Base
不能转为Derive
),Base*
和Derive*
可以互相转换,可以操作指针、引用以及对象 - reinterpret_cast : 类似于C风格的强制类型转换,谈不上什么安全,操作指针类型都能成功,只能操作指针和引用
- dynamic_cast : 主要用在继承结构中,可以支持RTTI类型识别的上下转换。基类指针转成相应的派生类对象指针的时候,会识别该指针是否能够进行转换,也是只能操作指针和引用
一、const_cast
首先写一段代码,将const int*
转换为int*
我们来看一下汇编指令
可以看到,由于const_cast是语言级别的,不产生任何额外的指令代码,这两句在汇编指令上没有区别,两行代码转换的汇编指令是完全一样的,但是在编译阶段会有所差别
我们再看,这时我们使用类型强转将const int*
转换为double*
,p1指向的内存空间是const int a的空间,但是p1解引用后访问的范围是4字节,这就不安全了,然而编译依然通过
我们使用const_cast将const int*
转换为char*
试试
这就不被允许了,因为指针解引用后访问的范围和指针指向的变量不一致,这会不安全
只要是指针访问的范围和实际指向的变量空间不一致,const_cast就不允许转换
再举一个例子:
const_cast的参数必须是指针或引用类型
二、static_cast
static_cast静态类型转换应该是我们用的最多的,提供编译器认为安全的类型转换,没有任何联系的类型之间的转换就被否定了,编译不通过
int num = 100;
char c1 = (char)num; // 字符d
char c2 = static_cast<char>(num); // 字符d
这样转换是没有问题的,因为int和char是有联系的,在ASCII码表中100对应字符d
我们试试将int*
转换为double*
编译不通过,因为int*
和double*
是没有任何联系的,所以static_cast不允许转换。如果static_cast允许转换了,指针访问的范围就变了,用指向8字节空间的指针访问4字节内存,会导致内存访问不安全
而我们用C风格的写法,是可以编译通过的
此外,static_cast还支持基类和派生类类型的转换,因为基类类型和派生类类型是继承结构上从上到下的类型,它们类型之间是有关系联系的
static_cast可以通过它们之间的互相转换,但是转换之后,代码到底安不安全是由开发者来保证,而不是由static_cast保证。
使用static_cast:
- 有联系的类型之间可以互相转换
- 没有任何联系的类型之间无法转换
- 基类类型与派生类类型进行转换,可以用static_cast,它们类型之间有关系,但不一定安全
三、reinterpret_cast
类似于C风格的强制类型转换,和内存访问安全就没有什么关系了
编译通过,但使用起来不安全
四、dynamic_cast
class Base {
public:
virtual void func() = 0;
};
class Derive1 : public Base {
public:
void func() { cout << "call Derive1::func " << endl; }
};
class Derive2 : public Base {
public:
void func() { cout << "call Derive2::func " << endl; }
};
void showFunc(Base* p) {
p->func(); // 动态绑定
}
int main(){
Derive1 d1;
Derive2 d2;
showFunc(&d1);
showFunc(&d2);
return 0;
}
Base里面是虚函数,是运行时的动态绑定,取的是RTTI的类型,就是指针指向的对象,进而访问其虚函数表,执行派生类的同名覆盖方法
但是随着项目的进行,软件开发的需求改变了,我们要增加新的需求:如果Base指针指向其他的Derive对象,就调用func方法,如果指向Derive2对象,就调用new_func方法
class Derive2 : public Base {
public:
void func() { cout << "call Derive2::func " << endl; }
void new_func() { cout << "call Derive2::new_func " << endl; }
};
如果现在的软件设计就要去实现这个功能,我们应该怎么做?
现在需要识别*p
的类型,到底指向的是哪个对象,如果是Derive2的对象, 就要调用new_func方法
我们可以通过typeid(*p).name() == "Derive2"
比较,判断p指向的对象,进而调用不同的函数,dynamic_cast就是通过这种方式进行RTTI类型的转换
void showFunc(Base* p) {
// 访问指针p指向的对象,访问对象内存中的vfptr,进而访问vftable,获取RTTI信息
// 如果RTTI类型是Derive2,就返回Derive2对象的地址,否则返回nullptr
Derive2* pd2 = dynamic_cast<Derive2*>(p);
if (pd2 != nullptr) {
pd2->new_func();
}
else {
p->func();
}
}
int main(){
Derive1 d1;
Derive2 d2;
showFunc(&d1);
showFunc(&d2);
return 0;
}
使用static_cast,是编译时期的类型转换,不会判断运行时期的RTTI类型,由于这是上下继承关系,是一定可以转换成功的,这就导致我们无法判断Base指针指向的到底是哪个派生类对象
void showFunc(Base* p) {
// 编译时期的类型转换
Derive2* pd2 = static_cast<Derive2*>(p);
if (pd2 != nullptr) {
pd2->new_func();
}
else {
p->func();
}
}
int main(){
Derive1 d1;
Derive2 d2;
showFunc(&d1);
showFunc(&d2);
return 0;
}
这种写法是肯定可以转换成功的,所以pd2不为空,无论Base指针导致指向的是哪个派生类对象,最后调用的肯定是Derive2::new_func。然而第一次调用showFunc传入的是Derive1对象的地址,还是调用了Derive2::new_func,这就很不安全