【问题标题】:Destructing stack object vs deleting heap-allocated object of non-final class with virtual functions使用虚函数破坏堆栈对象与删除非最终类的堆分配对象
【发布时间】:2025-12-12 11:05:02
【问题描述】:

假设您有一个带有虚函数和非虚析构函数的派生类,例如:

class Base
{
public:
    ~Base() {}
};

class Derived : public Base
{
public:
    Derived() {}
    virtual void foo() {}
};

假设您创建了一个Derived 类的堆分配对象并使用delete 关键字将其删除,例如:

int main()
{
    Derived *d = new Derived();
    delete d;
}

使用-Wall -Wdelete-non-virtual-dtor -Werror 标志编译此代码会抛出一个错误,这完全没问题,因为它最终可能会导致 UB。演示here.

显然,调用d 对象的析构函数是导致编译器错误的原因,因为以下代码具有相同的结果(至少在CLANG 上,GCC 与以下代码没有问题):

int main()
{
    Derived d;
    d.~Derived();
}

但如果我在堆栈上创建一个简单的对象,CLANG 和 GCC 都不会出现编译器错误:

int main()
{
    Derived d;
}

我们都知道Derived类的析构函数是在main函数的最后被调用的,但是为什么在这种情况下没有错误呢?

【问题讨论】:

  • 手动调用析构函数并不违法(即使这里不正确),那为什么会产生错误呢?
  • @super 我希望它会产生错误,因为delete 正在产生错误,如果我没记错的话,delete 会调用对象的析构函数。但是,可能仅在运行时多态的情况下才会引发错误,并且 CLANG 在手动调用堆栈对象上的析构函数时存在错误。
  • 恕我直言,编译器是对的。它不知道 d 是否指向 Derived 的实例,它可以指向 Derived 的派生类的对象。使用关键字 final。
  • 在这种情况下这不是错误,如果您尝试删除某些派生的后代,这是一个警告。这段代码不是格式错误的,只是-Werror 阻止它被编译。
  • @S.M.好的,现在我明白了。谢谢

标签: c++ destructor final virtual-functions


【解决方案1】:

当一个对象具有自动存储持续时间或者是一个类的成员时,不需要考虑多态性。在给出的代码中,左值d 不能引用派生更多的对象。因此,调用Derived::~Derived 总是正确的,不需要被警告。

【讨论】:

  • 所以这是 CLANG 的错误?
  • @NutCracker 当我谈到调用Derived::~Derived 时,我指的是编译器插入的隐式调用。无论如何,您在代码中显示的显式调用都是错误的,如果编译器出于某种原因发出警告,那就更好了。
  • 好的,我明白了。 Tnx