主要解释强制类型转换的影响。因为static_cast会在编译期间检测,dynamice_cast会在运行时检测。
#include <iostream> #include <hash_map> using namespace std; class I1 { public: virtual void vf1() { cout << "I'm I1:vf1()" << endl; } }; class I2 { public: virtual void vf2() { cout << "I'm I2:vf2()" << endl; } }; class C : public I1, public I2 { private: hash_map<string, string> m_cache; }; I1* CreateC() { return new C(); } int main(int argc, char** argv) { I1* pI1 = CreateC(); pI1->vf1();
I2* pI2 = (I2*)pI1; pI2->vf2();
delete pI1; return 0; } 上述代码执行结果如下:
先来看看前面代码的内存布局。
之所以会出现pI1和pI2指向了同一个地址,是因为C++编译器没有足够的知识来把IA*类型转换为IB*类型,只能按照传统的C指针强制转换处理,也就是指针位置不变。为了验证上面的结论,简单的把pIA和pIB打印出来即可。把main()函数修改为如下:
int main(int argc, char** argv)
{
I1* pI1 = CreateC();
pI1->vf1();
I2* pI2 = (I2*)pI1;
pI2->vf2();
cout << "pI1指向的地址为:"<<std::hex << pI1 << endl;
cout << "pI2指向的地址为:"<<std::hex << pI2 << endl;
delete pI1;
return 0;
}
执行结果为:
可见pI1和pI2确实指向了同一个地址,而这个地址就是I1类的虚表。由于虚函数是按照顺序定位的,编译器编译pI2->vf2()的时候,不管实际的pI2指向哪里,都把它当做指向了I2的虚表,根据I2类定义,推出I2::vf2()这个函数位于其虚表的第0个位置,所以就直接把pI2指向的地址作为vf2来调用。而实际上,这个位置恰恰是I1虚表的第0个位置,也就是I1::vf1的位置,所以实际执行时调用的是I1::vf1()。其实这种情况是有些特殊的,也就是这个位置正好也是一个函数地址,而且函数原型也一样,要是有任何不同的地方,就会造成调用失败,反而更容易及时的提醒开发者。
强制类型转换
static_cast:进行编译期类型转换,此时如果C++编译期不能推算出指针调整算法,就会报错,提醒开发者。
dynamic_cast:使用dynamic_cast进行运行期动态类型转换,这需要开启编译器的RTTI。
reinterpret_cast:地址的转换,不需要检测类型。
在C++中,指针的类型转换是经常发生的事情,比如将派生类指针转换为基类指针,将基类指针转换为派生类指针。指针的本质其实就是一个整数,用以记录进程虚拟内存空间中的地址编号,而指针的类型决定了编译器对其指向的内存空间的解释方式。C++中对指针进行类型转换,不会改变指针的值,只会改变指针的类型(即改变编译器对该指针指向内存的解释方式),但是这个结论在C++多重继承下是 不成立的。
#include <iostream>
using namespace std;
class CBaseA
{
public:
char m_A[32];
};
class CBaseB
{
public:
char m_B[64];
};
class CDerive : public CBaseA, public CBaseB
{
public:
char m_D[128];
};
int main()
{
auto pD = new CDerive;
auto pA = (CBaseA *)pD;
auto pB = (CBaseB *)pD;
cout << pA << '
' << pB << '
' << pD << endl;
cout << (pD == pB) << endl;
}
这段代码的输出是:
0x9f1080
0x9f10a0
0x9f1080
1
pB与pD的指针差值正好是CBaseA占用的内存大小32字节,而pA与pD都指向了同一段地址。这是因为,将一个派生类的指针转换成某一个基类指针,编译器会将指针的值偏移到该基类在对象内存中的起始位置。
输出1表示pD和pB是相等的,而刚刚我们才说明了,pD和pB的地址是相差了32个字节的。
其实这也是编译器为大家屏蔽了这种指针的差异,当编译器发现一个指向派生类的指针和指向其某个基类的指针进行==运算时,会自动将指针做隐式类型提升已屏蔽多重继承带来的指针差异。因为两个指针做比较,目的通常是判断两个指针是否指向了同一个内存对象实例,在上面的场景中,pD和pB虽然指针值不等,但是他们确确实实都指向了同一个内存对象(即new CDerive;产生的内存对象 ),所以编译器又在此处插了一脚,让我们可以安享==运算的上层语义。