2

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

#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;
}
4

4 回答 4

7

您所观察到的称为“未定义行为”。Base如果您想Child通过指针对实例调用 delete,则将 dtor设为虚拟Base

从 2003 年标准 5.3.5/3 开始:

在第一种选择(删除对象)中,如果操作数的静态类型与其动态类型不同,则静态类型应为操作数动态类型的基类,并且静态类型应具有虚拟析构函数或行为未定义.

于 2012-06-25T05:24:09.293 回答
5

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

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

于 2012-06-25T05:27:12.323 回答
1

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

#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() ,则打印所有析构函数的打印。

如果 virtual ~child() 或 virtual ~gChild(),则仅打印基本析构函数。

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

谢谢。

于 2012-06-25T05:45:43.467 回答
1

看看这个:

#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::方法),而虚拟方法遵循运行时类型链,因此如果 aBase*指向 a Derived,则该Derived方法将被调用.

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

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

于 2012-06-25T06:56:26.817 回答