为啥虚拟析构函数需要删除运算符

Posted

技术标签:

【中文标题】为啥虚拟析构函数需要删除运算符【英文标题】:Why is delete operator required for virtual destructors为什么虚拟析构函数需要删除运算符 【发布时间】:2015-10-19 14:39:59 【问题描述】:

在使用 g++ 的独立上下文中(没有标准库,例如在操作系统开发中)会发生以下现象:

class Base 
public:
   virtual ~Base() 
;

class Derived : public Base 
public:
    ~Derived() 
;

int main() 
    Derived d;

链接时它会声明如下内容:undefined reference to operator delete(void*)

这显然意味着 g++ 正在生成对删除运算符的调用,即使动态内存分配为零。如果析构函数不是虚拟的,则不会发生这种情况。

我怀疑这与为类生成的 vtable 有关,但我不完全确定。 为什么会这样?

如果由于缺少动态内存分配例程而不能声明删除操作符,有解决办法吗?

EDIT1:

为了成功重现我使用的 g++ 5.1 中的问题:

g++ -ffreestanding -nostdlib foo.cpp

【问题讨论】:

我无法重现这个简单示例的问题。你确定你没有遗漏什么吗? @RobinKrahl 您是否尝试将 -ffreestanding 添加到 g++ 命令行。如果有任何删除操作符的调用,请检查反汇编转储。 在我的 Linux Mint 上使用 g++ 4.8.4 编译。使用g++ Testing.cpp -ffreestanding。但是使用 clang 3.5.0 我得到了一堆链接器错误。 也许是一个愚蠢的问题:-nostdlib 做什么? (删除运算符 delete(void*) ?) @DieterLücking 它跳过链接标准 C++ 库(STL、默认运算符、个性、异常处理、堆栈展开等) 【参考方案1】:

因为删除析构函数。当您在具有虚拟析构函数的对象上调用 delete obj 时实际调用的函数。它调用完整的对象析构函数(链接基本对象析构函数——您实际定义的析构函数),然后调用operator delete。这样一来,在所有使用delete obj 的地方,只需要发出一个调用,并且还用于调用operator delete,其指针与从operator new 返回的指针相同,符合ISO C++ 的要求(尽管这也可以通过dynamic_cast 以更高的成本完成)。

它是 GCC 使用的 Itanium ABI 的一部分。

我认为你不能禁用它。

【讨论】:

感谢您的回答。同意@Yakk。现在我明白会发生什么。您认为是否有解决方法 @Felipe 因为删除析构函数只会从delete 调用,并且由于您没有,因此您可以实现自己的delete(void*) 并让它什么都不做或生成运行时错误。 @Felipe 不,删除析构函数只能用delete调用 @Ediac 的想法是提供一个虚拟函数来消除链接器错误,因为无论如何都不应该调用该函数。我建议让它生成一个错误,以便很容易检测到是否有人修改了代码并在将来尝试使用delete 我认为您没有(明确地)提到的另一个原因是:可以在派生类中重载operator delete;标准 [class.free]p4 要求 delete 动态(有效)调度如果delete 操作数的静态类型具有虚拟 dtor。也就是说,如果你定义了一个虚拟析构函数,你实际上也会得到一个虚拟的operator delete。 Live example【参考方案2】:

在 C++20 中现在有一个修复:P0722R3。 static void operator delete(T*, std::destroying_delete_t) 释放函数。它本质上映射到破坏性析构函数。

你可以让它不调用::operator delete,比如:

class Base 
public:
    void operator delete(Base* p, std::destroying_delete_t) 
        // Shouldn't ever call this function
        std::terminate();  // Or whatever abort-like function you have on your platform

        // The default implemenation without any overrides basically looks like:
        // p->~Base(); ::operator delete(p);
        // Which is why the call to `operator delete` is generated
    
    virtual ~Base() 
;

class Derived : public Base 
public:
    // Calls Base::operator delete in deleting destructor, so no changes needed
    ~Derived() 
;

int main() 
    Derived d;


删除析构函数是在您执行delete ptr_to_obj; 时调用的析构函数。它只能由delete 表达式调用,所以如果你的代码中没有,这应该没问题。如果你这样做了,你可以用::delete ptr_to_obj;替换它们,删除析构函数将不再被调用(它的目的是为类调用覆盖的operator delete,而::delete只会调用全局::operator delete

【讨论】:

以上是关于为啥虚拟析构函数需要删除运算符的主要内容,如果未能解决你的问题,请参考以下文章

为啥在析构函数中抛出异常时不调用重载删除?

C++11 虚拟析构函数和移动特殊函数的自动生成

C++ 虚拟析构函数 (virtual destructor)

为啥我们需要 C++ 中的纯虚析构函数?

为啥我在这里的析构函数中删除时创建了一个潜在的流浪指针?

当删除没有虚拟析构函数的多态对象时会发生啥?