文章目录

C语言中的强制类型转换通常不安全,根据强转的类型造成变量内存扩大或缩小,访问不安全

  • const_cast : 去掉(指针或者引用)const属性的一个类型转换,只能转换指针或引用类型
  • static_cast : 提供编译器认为安全的类型转换(没有任何联系的类型之间的转换无法通过编译)
  • reinterpret_cast : 类似于C风格的强制类型转换,谈不上什么安全
  • dynamic_cast : 主要用在继承结构中,可以支持RTTI类型识别的上下转换。基类指针转成相应的派生类对象指针的时候,会识别该指针是否能够进行转换

一、const_cast

首先写一段代码,将​​const int*​​​转换为​​int*​

C++语言级别四种类型转换_类型转换


我们来看一下汇编指令

C++语言级别四种类型转换_c++_02


可以看到,由于const_cast是语言级别的,不产生任何额外的指令代码,这两句在汇编指令上没有区别,两行代码转换的汇编指令是完全一样的,但是在编译阶段会有所差别我们再看,这时我们直接将​​const int*​​​转换为​​double*​​,p1指向的内存空间是const int a的空间,但是p1解引用后访问的范围是4字节,这就不安全了,然而编译依然通过

C++语言级别四种类型转换_派生类_03


C++语言级别四种类型转换_开发语言_04


我们使用const_cast将​​const int*​​​转换为​​char*​​试试

C++语言级别四种类型转换_c++_05


C++语言级别四种类型转换_c++_06


这就不被允许了,因为指针解引用后范文的范围和指针指向的变量不一致,这会不安全

C++语言级别四种类型转换_类型转换_07


C++语言级别四种类型转换_开发语言_08


只要是指针访问的范围和实际指向的变量空间不一致,就不允许转换再举一个例子:

C++语言级别四种类型转换_强制类型转换_09


C++语言级别四种类型转换_c++_10


const_cast的参数必须是指针或引用类型

二、static_cast

static_cast静态类型转换应该是我们用的最多的,提供编译器认为安全的类型转换,没有任何联系的类型之间的转换就被否定了,编译不通过

C++语言级别四种类型转换_类型转换_11


这样转换是没有问题的,因为int和char是有联系的,在ASCII码表中97对应字符a我们试试将​​int*​​​转换为​​double*​

C++语言级别四种类型转换_强制类型转换_12


C++语言级别四种类型转换_开发语言_13


编译不通过,因为​​int*​​和​​double*​​是没有任何联系的,所以static_cast不允许转换。如果static_cast允许转换了,指针访问的范围就变了,用指向8字节空间的指针访问4字节内存,会导致内存访问不安全

C++语言级别四种类型转换_c++_14


而我们用C风格的写法,是可以编译通过的

此外,static_cast还支持基类和派生类类型的转换,因为基类类型和派生类类型是继承结构上从上到下的类型,它们类型之间是有关系联系的

static_cast可以通过它们之间的互相转换,但是转换之后,代码到底安不安全是由开发者来保证,而不是由static_cast保证。

使用static_cast:

  1. 有联系的类型之间可以互相转换
  2. 没有任何联系的类型之间无法转换
  3. 基类类型与派生类类型进行转换,可以用static_cast,它们类型之间有关系,但不一定安全

三、reinterpret_cast

类似于C风格的强制类型转换,和内存访问安全就没有什么关系了

C++语言级别四种类型转换_派生类_15


编译通过,但使用起来不安全

四、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的类型,就是指针指向的对象,进而访问其虚函数表,执行派生类的同名覆盖方法

C++语言级别四种类型转换_派生类_16


但是随着项目的进行,软件开发的需求改变了,我们要增加新的需求:如果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;
}

C++语言级别四种类型转换_类型转换_17


使用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;
}

C++语言级别四种类型转换_类型转换_18


这种写法是肯定可以转换成功的,所以pd2不为空,无论Base指针导致指向的是哪个派生类对象,最后调用的肯定是Derive2::new_func。然而第一次调用showFunc传入的是Derive1对象的地址,还是调用了Derive2::new_func,这就很不安全