以下内容在初步了解c++多态的知识后再阅读更佳。
案例源码:
class Base1 {
public:
Base1() {
b1 = 111;
}
public:
virtual void fun1() {
cout << "Base1:fun1" << b1 << " " << this << endl;
}
virtual void fun2() {
cout << "Base1:fun2" << b1<<" " << this << endl;
}
protected:
int b1;
int a[100];
};
class Base2 {
public:
Base2() {
b2 = 222;
}
virtual void fun1() {
cout << "Base2:fun1" << b2<<" "<<this << endl;
}
virtual void fun2() {
cout << "Base2:fun2" << b2 <<" "<< this << endl;
}
int b2;
};
class Derive1 :public Base1, public Base2 {
public:
Derive1() {
d1 = 123;
};
void nfun1() {
cout << "Derive1:nfun1" << endl;
}
virtual void fun1() {
cout << "Derive1:fun1" <<" "<<this<<" "<< d1 << endl;
}
virtual void fun3() {
cout << "Derive1:fun3" << " " << this << " " << endl;
}
int d1;
};
int main()
{
int a;
Derive1 d1;
d1.nfun1();
void* pd1 = &d1;
Base2* pB = (Base2 *)pd1;
Base2* pB2 = &d1;
cout << pB << " " << pB2 << " "<< &d1<< endl;
//提问:以下四个基类指针调用的函数输出是什么?
pB->fun2();
pB2->fun2();
pB->fun1();
pB2->fun1();
}
对于源码中注释的问题:
最开始我以为pB->fun2和pB2->fun2都是Base2::fun2;pB->fun1和pB2->fun1则都是Derive1::fun1。
但实际的输出结果是:
Derive1:nfun1
0000008CCB72F910 0000008CCB72FAB0 0000008CCB72F910
Base1:fun2111 0000008CCB72F910
Base2:fun2222 0000008CCB72FAB0
Derive1:fun1 0000008CCB72F910 123
Derive1:fun1 0000008CCB72F910 123
这里我最开始有一个问题:
为什么Base2的指针调用的函数 pB->fun2() 的结果是Base2::fun2 ?
分析:
在多继承中,Derive1对象的内存分布是这样的(VS的监视窗口)
按照继承顺序,内存分布如下:
- Derive1:Base1的虚函数指针
- Base1 非static成员变量
- Derive1:Base2的虚函数指针
- Base2 非static成员变量
在上面的例子中,由于pB是通过强转赋值d1的地址,pB在调用虚函数时,用到的虚函数指针就会是Derive1:Base1的虚函数指针,所以调用了Base1::fun2虚函数。而pB2被编译器赋值d1+sizeof(Base1),这个地址实际上是Derive1:Base2虚函数的指针的地址,所以调用了Base2::fun2。
这个结果理解之后,我又有一个问题了:
既然pB和pB2不一样,为什么调用的fun1打印的this指针是一样的?(按我的理解应该分别打印pB和pB2的地址)
细心的同学可能在上面那张VS的监控窗口中发现了一个点:循环表中代替Base2::fun1的函数不是Derive1::fun1而是Derive1::fun1`adjustor{416}`,到这里就可以猜测这个函数把this从原本的&d1+sizeof(Base1)调整回&d1之后再调用真正的Derive1::fun1。
为了验证猜测,我们在pB2->fun1断点后打开反汇编(VS的调试->窗口->反汇编)。
一般this是通过rcx寄存器来传递给非static成员函数的, 见上图红框。
rax寄存器则存放了虚函数Derive1::fun1`adjustor{416}`地址,不方便看,我们直接从监视窗口拷贝Derive1::fun1`adjustor{416}\ 的地址 0x00007ff76fa21113,在反汇编中跳转至该汇编地址,步进到某步可以看到:
这里对rcx减了一个值1A0h(416), 即把this减了1A0h 变成了 d1的地址,然后才调用的真正的fun1,验证了之前的猜测。
总结:在使用 c++的多继承配合多态时,应该谨慎一些,避免强转指针导致运行出现错误的行为,甚至可以少使用多继承,考虑用组合方式代替。