对于多继承下的内存布局问题,请参考: 。其实这里的问题也是与内存不就息息相关,也算是对前面这篇博文的一点补充。前面博文指出了使用同一对象调用不同的函数时,在被调用函数内部的this指针是不同的,以及为什么不同然而没有说明这里的this是何时被确定的,是编译时?还是运行时?
还是先来看看前面代码的内存布局。
之所以会出现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()。其实这种情况是有些特殊的,也就是这个位置正好也是一个函数地址,而且函数原型也一样,要是有任何不同的地方,就会造成调用失败,反而更容易及时的提醒开发者。如下代码所示:
#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;
}
virtual void vf3()
{
cout << "I'm I2:vf3()" << 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();
pI2->vf3();
cout << "pI1指向的地址为:"<<std::hex << pI1 << endl;
cout << "pI2指向的地址为:"<<std::hex << pI2 << endl;
delete pI1;
return 0;
}
此时的内存布局为:
在执行pI2->vf2()时,执行的是I1::vf1(),但是不会报错。当执行pI2->vf3();时,由于调用的地址并不是一个函数指针,所以会报错。