为什么建议设置虚析构函数

虚析构函数

引入

在多态中,我们一般建议将析构函数设置为虚函数,这是为什么呢?

举例

问题

#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指针,就不会出现问题。

  • 8
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

落后的炫幕あ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值