虚析构函数
引入
在多态中,我们一般建议将析构函数设置为虚函数,这是为什么呢?
举例
问题
#include <iostream>
using namespace std;
class Donkey
{
public:
int m_donkeyAge;
public:
Donkey()
{
printf("Donkey::Donkey()的this指针是: %p\n", this);
}
virtual void vfuncDonkey()
{
cout << "I am Donkey\n";
}
};
class Horse
{
public:
int m_horseAge;
public:
Horse()
{
printf("Horse::Horse()的this指针是: %p\n", this);
}
virtual void vfuncHorse()
{
cout << "I am Horse\n";
}
};
class Mule : public Donkey, public Horse
{
public:
int m_muleAge;
int m_muleHeight;
public:
Mule()
: m_muleAge(10), m_muleHeight(20)
{
printf("Mule::Mule()的this指针是: %p\n", this);
}
virtual void vfuncHorse()
{
cout << "I am Mule\n";
}
~Mule()
{
int a = 0;
cout << "free Mule\n";
}
};
int main()
{
Mule mule;
mule.m_donkeyAge = 10;
mule.m_horseAge = 20;
mule.m_muleAge = 30;
mule.m_muleHeight = 40;
cout << endl;
Horse *pObj1 = new Mule();
cout << endl;
Mule *pObj2 = (Mule*)pObj1;
cout << endl;
delete pObj1;
}
输出:
Donkey::Donkey()的this指针是: 0x7ffdb79e3b30
Horse::Horse()的this指针是: 0x7ffdb79e3b40
Mule::Mule()的this指针是: 0x7ffdb79e3b30
Donkey::Donkey()的this指针是: 0x557d575c72c0
Horse::Horse()的this指针是: 0x557d575c72d0
Mule::Mule()的this指针是: 0x557d575c72c0
free(): invalid pointer
Aborted
可以看到程序崩溃了,崩溃的刚好就是delete pObj1;
这一句。接下来分析为什么。
这里需要注意的是Horse::Horse()
和Donkey::Donkey()
的this指针相差0x10 == 16个字节,这是由于虚函数表指针的缘故。
gdb分析
Step1
In file: /home/ubuntu/GithubFile/Code/CPP/objectmodel/virtual_function_test6.cpp
72 Mule mule;
73 mule.m_donkeyAge = 10;
74 mule.m_horseAge = 20;
75 mule.m_muleAge = 30;
76 mule.m_muleHeight = 40;
► 77 cout << endl;
78
79 Horse *pObj1 = new Mule();
80
81 cout << endl;
82
pwndbg> p mule
$1 = (Mule) {
<Donkey> = {
_vptr.Donkey = 0x555555557ca8 <vtable for Mule+16>,
m_donkeyAge = 10
},
<Horse> = {
_vptr.Horse = 0x555555557cc8 <vtable for Mule+48>,
m_horseAge = 20
},
members of Mule:
m_muleAge = 30,
m_muleHeight = 40
}
pwndbg> p &mule
$2 = (Mule *) 0x7fffffffe4b0
pwndbg> x /5gx 0x7fffffffe4b0
0x7fffffffe4b0: 0x0000555555557ca8 0x000055550000000a
0x7fffffffe4c0: 0x0000555555557cc8 0x0000001e00000014
0x7fffffffe4d0: 0x0000000000000028
Step2
In file: /home/ubuntu/GithubFile/Code/CPP/objectmodel/virtual_function_test6.cpp
76 mule.m_muleHeight = 40;
77 cout << endl;
78
79 Horse *pObj1 = new Mule();
80
► 81 cout << endl;
82
83 Mule *pObj2 = (Mule*)pObj1;
84 cout << endl;
85
86 delete pObj1;
pwndbg> p pObj1
$3 = (Mule *) 0x55555556b2c0
pwndbg> p &pObj1
$4 = (Horse **) 0x7fffffffe4a0
pwndbg> x /gx 0x7fffffffe4a0
0x7fffffffe4a0: 0x000055555556b2d0
- 0x55555556b2c0是new Mule()的值;
- pObj1保存的是0x000055555556b2d0 = 0x55555556b2c0 + 0x10,也就是编译器对this指针进行了调整。
- 所以我们在堆上申请的内存地址是0x55555556b2c0,但是释放的却是0x000055555556b2d0,不会出错才怪。
正确的做法就是设置虚析构函数,这样调用虚析构函数的时候,this指针能自动调整回去。
Step3
In file: /home/ubuntu/GithubFile/Code/CPP/objectmodel/virtual_function_test6.cpp
79 Horse *pObj1 = new Mule();
80
81 cout << endl;
82
83 Mule *pObj2 = (Mule*)pObj1;
► 84 cout << endl;
85
86 delete pObj1;
87 }
pwndbg> p pObj2
$5 = (Mule *) 0x55555556b2c0
可以看出这里的转换Mule *pObj2 = (Mule*)pObj1;
就是调整了一下this指针。如果我们delete pObj2,那么就不会出错。
修改后的代码
#include <iostream>
using namespace std;
class Donkey
{
public:
int m_donkeyAge;
public:
Donkey()
{
printf("Donkey::Donkey()的this指针是: %p\n", this);
}
virtual ~Donkey()
{
}
virtual void vfuncDonkey()
{
cout << "I am Donkey\n";
}
};
class Horse
{
public:
int m_horseAge;
public:
Horse()
{
printf("Horse::Horse()的this指针是: %p\n", this);
}
virtual ~Horse()
{
}
virtual void vfuncHorse()
{
cout << "I am Horse\n";
}
};
class Mule : public Donkey, public Horse
{
public:
int m_muleAge;
int m_muleHeight;
public:
Mule()
: m_muleAge(10), m_muleHeight(20)
{
printf("Mule::Mule()的this指针是: %p\n", this);
}
virtual void vfuncHorse()
{
cout << "I am Mule\n";
}
~Mule()
{
int a = 0;
cout << "free Mule\n";
}
};
int main()
{
Mule mule;
mule.m_donkeyAge = 10;
mule.m_horseAge = 20;
mule.m_muleAge = 30;
mule.m_muleHeight = 40;
cout << endl;
Horse *pObj1 = new Mule();
cout << endl;
Mule *pObj2 = (Mule*)pObj1;
cout << endl;
delete pObj1;
}
输出:
Donkey::Donkey()的this指针是: 0x7fff56f69460
Horse::Horse()的this指针是: 0x7fff56f69470
Mule::Mule()的this指针是: 0x7fff56f69460
Donkey::Donkey()的this指针是: 0x5593a0f972c0
Horse::Horse()的this指针是: 0x5593a0f972d0
Mule::Mule()的this指针是: 0x5593a0f972c0
free Mule
free Mule
这里 delete pObj1;
的时候会自动调整this指针,就不会出现问题。