使用虚函数销毁堆栈对象与删除非最终类的堆分配对象

问题描述

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

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 函数的末尾调用的,但是为什么在这种情况下没有错误呢?

解决方法

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