基类析构函数不是虚拟的,子类析构函数是虚拟的,程序崩溃



为什么下面的程序会崩溃?我有一个基类,其析构函数不是虚拟的,但子类析构函数是虚拟的:

#include <iostream>
class Base {
public:
  Base() {
    std::cout << "Base::Base CTOR " << std::endl;
  }
  ~Base() {
    std::cout << "Base::Base DTOR " << std::endl;
  }
};
class Child : public Base {
public:
  Child(){
    std::cout << "Child::Child CTOR " << std::endl;
  }
  virtual ~Child() {
    std::cout << "Child::Child DTOR " << std::endl;
  }
};
int main (int argc, char **argv) {
  Base *ptr = new Child;
  delete ptr;
}

你观察到的被称为"未定义的行为"。如果要通过指针在实例上调用删除Child Base请将Base的 dtor 设置为虚拟。

从 2003 年标准 5.3.5/3:

在第一个备选方案(删除对象)中,如果静态类型 操作数不同于其动态类型,静态类型应为 操作数的动态类型和静态类型的基类应 具有虚拟析构函数或行为未定义。

您有未定义的行为,因为要删除的指针操作数的静态类型与它指向的对象的动态类型不匹配,并且您不满足此规则的例外要求,该例外允许将指针传递给要删除的对象的基类,因为此异常要求类具有虚拟析构函数。

任何行为都是可能的,包括代码"按预期"工作或崩溃。

希望这个例子能帮助你明白这一点:

#include <iostream>
class Base {
public:
 Base() {
    std::cout << "Base::Base CTOR " << std::endl;
 }
 ~Base() {
   std::cout << "Base::Base DTOR " << std::endl;
 }
private:
protected:
};
class Child : public Base {
 public:
 Child(){
std::cout << "Child::Child CTOR " << std::endl;
  }
  ~Child(){
std::cout << "Child::Child DTOR " << std::endl;
 }
  private:
 protected:
 };
  class gChild : public Child {
   public:
   gChild(){
    std::cout << "Child::Child gCTOR " << std::endl;
   }
  ~gChild(){
    std::cout << "Child::Child gDTOR " << std::endl;
  }
private:
protected:
};
int main ( int argc, char **argv) {
    Base *ptr = new gChild;
 delete ptr;
}

如果虚拟 ~Base() ,则打印所有析构函数的打印。

如果虚拟 ~child() 或虚拟 ~gChild(),则只打印基本析构函数。

这是因为析构函数以相反的方向执行,而这里的行为是未定义的。必须定义基本析构函数虚拟才能获得预期结果。

谢谢。

看看这个:

#include <iostream>
class Base
{
public:
    void nonvirtualmethod()
    { std::cout << "Base nonvirtualmethod" << std::endl; }
    virtual void virtualmethod()
    { std::cout << "Base virtualmethod" << std::endl; }
};
class Derived: public Base
{
public:
    void nonvirtualmethod()
    { std::cout << "Derived nonvirtualmethod" << std::endl; }
    virtual void virtualmethod()
    { std::cout << "Derived virtualmethod" << std::endl; }
};
int main()
{
    Derived d;
    Derived* pd = &d;
    Base* pb = &d;    //< NOTE: both pd and pb point to the same object
    pd->nonvirtualmethod();
    pb->nonvirtualmethod();
    pd->virtualmethod();
    pb->virtualmethod();
}

我给你以下输出:

Derived nonvirtualmethod
Base nonvirtualmethod
Derived virtualmethod
Derived virtualmethod  //< invoked by a Base*

这是因为pb指针的静态类型(Base*)之间存在差异以及它指向的动态类型 ( Derived )。虚拟方法和普通方法之间的区别在于,非虚拟方法遵循静态类型映射(因此Base指针调用Base::方法),而虚拟方法遵循运行时类型的链,因此如果Base*指向Derived,则将调用Derived方法。

从这个意义上说,析构函数没有什么特别的:如果它不是虚拟的,Base指针将不会调用Derived指针,因此你留下了一个半销毁的对象,该对象被送回内存存储。

之所以是UB(而不是简单地否认),是因为"内存存储"不是由语言本身管理的,而是由程序托管的平台管理的:崩溃很可能取决于缺少Derived部分(仍然活着)将导致操作系统尝试释放具有错误起始地址的内存块。

最新更新