多重继承下的类型转换

主要解释强制类型转换的影响。因为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;
}
上述代码执行结果如下:

先来看看前面代码的内存布局。

20140425110212046

之所以会出现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; 

执行结果为:

20140425110720890

可见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 << '\n' << pB << '\n' << pD << endl;
cout << (pD == pB) << endl;
}

这段代码的输出是:

0x9f1080
0x9f10a0
0x9f1080
1

20140716154534734

pB与pD的指针差值正好是CBaseA占用的内存大小32字节,而pA与pD都指向了同一段地址。这是因为,将一个派生类的指针转换成某一个基类指针,编译器会将指针的值偏移到该基类在对象内存中的起始位置。

 

输出1表示pD和pB是相等的,而刚刚我们才说明了,pD和pB的地址是相差了32个字节的。

其实这也是编译器为大家屏蔽了这种指针的差异,当编译器发现一个指向派生类的指针和指向其某个基类的指针进行==运算时,会自动将指针做隐式类型提升已屏蔽多重继承带来的指针差异。因为两个指针做比较,目的通常是判断两个指针是否指向了同一个内存对象实例,在上面的场景中,pD和pB虽然指针值不等,但是他们确确实实都指向了同一个内存对象(即new CDerive;产生的内存对象 ),所以编译器又在此处插了一脚,让我们可以安享==运算的上层语义。

 

参考地址:http://blog.csdn.net/smstong/article/details/24455371

posted on 2016-02-03 11:21  cthu  阅读(1375)  评论(0编辑  收藏  举报

导航