先看看单继承下,不加虚析构函数的情况:
#include<iostream>
using namespace std;
class basic
{
public:
virtual void go()
{
cout << "basic::go\n";
}
~basic(){ cout << "~basic()\n"; }
};
class Child :public basic
{
public:
virtual void go()
{
cout << "Child::go\n";
}
~Child(){ cout << "~Child()\n"; }
};
void main()
{
{
basic *c = new Child;
delete c;
}
system("pause");
}
结果:
给父类base析构函数加virtual,如下:
virtual ~basic(){ cout << "~basic()\n"; }
结果:
一、多重继承第二基类对虚函数支持的影响(this指针调整作用)
子类继承了几个父类,子类就有几个虚函数表
this指针调整的目的就是让对象指针正确的指向对象的首地址,从而能正确的调用对象的成员函数或者说正确确定数据成员的存储位置。
看如下代码:
class Base
{
public:
Base()
{
printf("Base::this = %p\n",this);
}
virtual void f()
{
cout << "Base:f()" << endl;
}
virtual void g()
{
cout << "Base:g()" << endl;
}
virtual void h()
{
cout << "Base:h()" << endl;
}
virtual ~Base()
{
cout << "Base::~Base()" << endl;
}
};
class Base2
{
public:
virtual void hBase2()
{
cout << "Base2:hBase2" << endl;
}
virtual ~Base2()
{
cout << "Base2::~Base2()" << endl;
}
};
class Derive :public Base, public Base2
{
public:
Derive()
{
printf("Derive::this = %p\n", this);
}
virtual void g()
{
cout << "Derive:g()" << endl;
}
virtual void i()
{
cout << "Derive:g()" << endl;
}
~Derive()
{
cout << "Derive::~Derive()" << endl;
}
};
1.通过指向第二个 基类的指针调用继承类的虚函数时
Base2 *pb2 = new Derive;
delete pb2;
上一节已经说过了,pb2指向Derive对象中的第二基类(Base2),所以为了能够正确执行derive的析构函数,pb2必须要调整指向Derive对象首地址(减掉4)。
2.一个指向派生类的指针调用第二基类中的虚函数时
Derive *pd= new Derive;
pd->hBase2();
delete pd;
上述代码hBase2()是第二基类中函数,要想成功调用,必须对this指针进行调整,让this指针指向Base2的类子对象的首地址。
看下面反汇编代码:
其中有一句 add ecx,4就是说this指向向下移动4个字节(+4)。这也及时Base2的首地址。
3.允许虚函数的返回值类型有所变化时
分别在Base,Base2,Dervie中增加如下的代码:
virtual Base *clone() const
{
return new Base();
}
virtual Base2 *clone() const
{
return new Base2();
}
virtual Derive *clone() const
{
return new Derive();
}
void main()
{
Base2 *pb1 = new Derive;
Base2 *pb2 = pb1->clone();
system("pause");
}
其中
pb1->clone();//将执行Derive::clone()
因为它是虚函数,动态绑定。执行时,pb1首先会将this指针调整到Derive对象的开始地址,这样调用的才是Derive的clone(),所以this指针应该减小。
可以看到有两个thunk,转到反汇编:
注意那个+8,其实也就是调用虚函数表中[2]
(也就是第二个thunk),继续执行到call
注意sub ecx,4这就意味着this指针往回走了4个字节,正好走到了真实的Derive对象的首地址,然后jmp跳转到Derive::clone().
执行完Derive::clone()后返回的是Derive对象的指针,但是是用Base2 *去接的,这样就还需要进行一个this指针调整,也就是往后走4个字节(+4),让其指向Base2对象的首地址。
二、虚继承下的虚函数
看如下代码:
class Base
{
public:
int m_nbase1;
virtual void f()
{
cout << "Base:f()" << endl;
}
virtual ~Base()
{
cout << "Base::~Base()" << endl;
}
};
class Derive :public virtual Base
{
public:
int m_nderive;
~Derive()
{
cout << "Derive::~Derive()" << endl;
}
};
void main()
{
cout << "sizeof(Derive) = " << sizeof(Derive) << endl;
Derive dobj;
dobj.m_nbase1 = 2;
dobj.m_nderive = 5;
Derive *pdobj = new Derive;
pdobj->f();
system("pause");
}
加个断点,位置如下:
切换到内存,如下:
因为是16个字节,所以我们只看16个字节,如下:
从这个布局我们可以推算出如下的布局:
其实上面连个问号,一个是虚函数表指针,一个是虚基类表指针,只是不知道哪个在哪个位置,接下来就是如何确定他们的位置。
接下来这么调用:
Derive *pdobj = new Derive;
pdobj->m_nbase1 = 2;
pdobj->m_nderive = 5;
pdobj->f();
由前面的内容,已经知道了第,第2,第4格的内容分别是5,2。那虚函数表的地址,不是第1格,就在第3格子,假设在第3格,
下面是pdobj的内存地址:
下面是pdobj的内存,我们查看它第三格的地址,也就是如下的地址:0x002becbc (和显示的反着),接下来去这个地址看看:
反过来就是002b1398
看看调用f()的汇编代码:
下面是寄存器的地址
其实eax中的值也就是f()的地址。如下证明:
这和第三格里面的内容一致,所以虚函数表指针在第三格,所以布局如下: