一个例子:
#include <iostream>
using namespace std;
class Base1{
public:
virtual void foo1() {};
};
class Base2{
public:
virtual void foo2() {};
};
class MI : public Base1, public Base2{
public:
virtual void foo1 () {cout << "MI::foo1" << endl;}
virtual void foo2 () {cout << "MI::foo2" << endl;}
};
int main(){
MI oMI;
Base1* pB1 = &oMI;
pB1->foo1();
Base2* pB2 = (Base2*)(pB1); // 指针强行转换,没有偏移
pB2->foo2();
pB2 = dynamic_cast<Base2*>(pB1); // 指针动态转换,dynamic_cast帮你偏移
pB2->foo2();
return 0;
}
我们希望输出的结果:
MI::foo1
MI::foo2
MI::foo2
实际结果:
MI::foo1
MI::foo1
MI::foo2
出现这样结果的原因:
首先,C++使用一种称之为vtable(google “vtable” for more details)的东西实现virtual函数多态调用。vtable每个类中都有一个,该类的所有对象公用,由编译器帮你生成,只要有virtual函数的类,均会有vtable。在继承过程中,由于类Base1和类Base2都有vtable,所以类MI继承了两个vtable。简单的分析一下对象oMI内存结构,如下:
其实很简单,就两个vtable的指针,0和4代表相对地址,指针地址大小为4。
pB1的值为0(pB1 == 0),所以调用“pB1->foo1()”时,可以正确的找到MI::fool这个函数执行。
但是当使用强行转换,将pB1转给pB2,那么实质上pB2的值也是0(pB2 == 0),当调用“pB2->foo2()”时,无法在第一个vtalbe中找到对应的函数,但是却不报错,而是选择执行函数MI::foo1,不知道为什么会有这种行为,但是这种行为却十分恶心,导致结果无法预期的(最后调用的函数会与函数申明的循序有关),不太会引起注意,使得bug十分隐晦。
当使用动态转换时,也就是“pB2 = dynamic_cast(pB1)”,dynamic_cast函数会根据尖括号中的类型进行指针偏移,所以pB2的值为4(pB2 == 4),这样调用“pB2->foo2()”就会按照期望的方式执行。
结论
上面的现象在单继承中是不会出现的,因为只有一个vtable(子类的virtual函数会自动追加到第一个父类的vtable的结尾)。所以不会出现上面的现象,而多重继承却出现了上面的想象,所以需要注意以下两点:
1. 多重继承需要慎用
2. 2. 类型转换尽量采用c++内置的类型转换函数,而不要强行转换