为什么虚析构函数需要使用delete运算符

14
在一个独立的情境中(没有标准库,例如在操作系统开发中),使用g++会发生以下现象:
class Base {
public:
   virtual ~Base() {}
};

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

int main() {
    Derived d;
}

链接时报错如下:undefined reference to operator delete(void*)

这很明显意味着,即使没有动态内存分配,g++仍然会生成对删除操作符的调用。如果析构函数不是虚函数,则不会出现这种情况。

我怀疑这与类的生成虚表有关,但我不完全确定。为什么会发生这种情况?

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

编辑1:

为了在g++ 5.1中成功复现问题,我使用了以下命令:

g++ -ffreestanding -nostdlib foo.cpp


我无法重现这个简单示例的问题。你确定没有错过什么吗? - Robin Krahl
使用g++ 4.8.4在我的Linux Mint上编译。使用g++ Testing.cpp -ffreestanding命令。但是使用clang 3.5.0时,我遇到了一堆链接器错误。 - ChajusSaib
它跳过链接标准C++库(STL、默认操作符、personality、异常处理、栈展开等)。 - StenSoft
@DieterLucking 是的。通常情况下,C++程序会链接到C++库,但是在开发操作系统时,您没有任何这些库,因此没有默认实现运算符delete。 - felknight
@StenSoft 对不起,我误读了你的评论。 - M.M
显示剩余2条评论
2个回答

15

由于删除了析构函数。这些函数实际上是在使用带有虚拟析构函数的对象调用 delete obj 时调用的。 它调用完整的对象析构函数(链式基对象析构函数-您实际定义的那些)然后调用 operator delete 。 这样,在使用 delete obj 的所有地方,只需要发出一个调用,并且也用于像ISO C ++所要求的那样使用与从 operator new 返回的相同指针调用 operator delete (尽管这也可以通过 dynamic_cast 更昂贵地完成)。

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

我不认为可以禁用此功能。


感谢您的回答。同意@Yakk的观点。现在我明白发生了什么。您认为有没有解决方法? - felknight
1
@Felipe,由于删除析构函数只会在delete中被调用,而你没有一个,你可以实现自己的delete(void*)并使其不执行任何操作或生成运行时错误。 - Mark Ransom
2
@Felipe 不,析构函数只有在使用 delete 时才会被调用。 - StenSoft
1
@Ediac 的想法是提供一个虚拟函数来消除链接器错误,因为该函数永远不应该被调用。我建议让它生成一个错误,这样如果有人修改代码并尝试在未来使用 delete,就很容易检测到。 - Mark Ransom
4
我认为你没有明确提到的一个额外原因是:在派生类中有可能重载 operator delete ; 标准 [class.free]p4 要求如果 delete 操作数的静态类型 有一个虚拟析构函数,则需要动态分发 delete(实际上)。也就是说,如果你定义了虚拟析构函数,则还将有效获得虚拟 operator delete。【示例链接】(http://coliru.stacked-crooked.com/a/0cb0d7aeced8caa0) - dyp
显示剩余4条评论

1
在C++20中现在有了一个修复方法: P0722R3。静态的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)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接