下面说说C++多重继承中关于指针的一些问题。
指针指向问题
先看下面的程序:
class Base1
{
public:
virtual void fun1() {cout << "Base1::fun1" << endl;};
};
class Base2
{
public:
virtual void fun2() {cout << "Base2::fun1" << endl;};
};
class Derive : public Base1, public Base2
{
public:
virtual void fun1 () {cout << "Derive::fun1" << endl;}
virtual void fun2 () {cout << "Derive::fun2" << endl;}
};
int main()
{
Derive oD;
Base1 *pB1 = (Base1*)(&oD);
Base2 *pB2 = (Base2*)(&oD);
cout << "&oD=" << &oD << '\n';
cout << "pB1=" << pB1 << '\n';
cout << "pB2=" << pB2 << '\n';
if (&oD == pB1) cout << "&oD == pB1" << '\n';
if (&oD == pB2) cout << "&oD == pB2" << '\n';
}
我电脑上的运行结果:
首先,可以看到&oD和pB1指针指向相同的存储地址。为什么?
这是因为当我们new一个Derive类的时候,计算机给Derive类分配的空间可以分为三部分:首先是类Base1的部分,然后是Base2的部分,然后是Derive中除去Base和Base2剩余部分,如下图。
Base1 |
Base2 |
Derive |
所以&oD肯定保存的是整体的首地址,而pB1指向的是Base1的首地址,恰好也是整体的首地址,所以有&oD和pB1的值刚好相等。pB2则指向的是Base2的首地址。
可是后面为什么会有&oD == pB2呢?这是因为当编译器发现一个指向派生类的指针和指向其某个基类的指针进行==运算时,会自动将指针做隐式类型提升已屏蔽多重继承带来的指针差异。因为两个指针做比较,目的通常是判断两个指针是否指向了同一个内存对象实例,在上面的场景中,&oD和pB2虽然指针值不等,但是他们确确实实都指向了同一个内存对象(即new Derive产生的内存对象)。
指针类型转换问题
还是使用上面的类,看主函数:
int main(){
Derive oD;
cout << "&oD=" << &oD << '\n';
Base1 *pB1 = &oD;
cout << "pB1=" << pB1 << '\n';
pB1->fun1();
cout << endl;
Base2 *pB2 = (Base2*)(pB1); // 指针强行转换,没有偏移
cout << "pB2=" << pB2 << '\n';
pB2->fun2();
cout << endl;
pB2 = dynamic_cast<Base2*>(pB1); // 指针动态转换,dynamic_cast帮你偏移
cout << "pB2=" << pB2 << '\n';
pB2->fun2();
return 0;
}
猜猜执行结果:
是不是很意外,为什么pB2->fun2()的结果是Derive::fun1。这里我们看到的是使用强制类型转换是不能把Base1类型的指针转成Base2类型的指针的,必须使用dynamic_cast的形式进制转换才奏效。
下面我们探索下为什么输出的是Derive::fun1。
我们修改Base1的定义:
class Base1
{
public:
virtual void fun3() {cout << "Base1::fun3" << endl;};
virtual void fun1() {cout << "Base1::fun1" << endl;};
};
给添加一个函数fun3,然后再次执行上面的main函数,结果如下:
我们可以发现强制转换不会成功,也不会报错,你调用Base2的fun2函数,因为强制转换不成功,所以指针仍然指向Base1,而Base1中没有fun2,所以就会自动调用声明的函数中的第一个函数。(不知道为什么会这样设计!)
上面强制将Base1转为Base2不会报错,但是不能运行处正确结果。而我们强制将Base2转为Base1呢?
int main()
{
Derive *pD = new Derive();
Base2 *pB2 = pD;
pB2->fun2();
Base1 *pB1 = (Base1*)(pB2);
pB1->fun1();
return 0;
}
这样程序执行到第6行的时候会直接奔溃。
所以:
1. C++多重继承需要慎用
2. 类型转换尽量采用c++内置的类型转换函数,而不要强行转换。