abi*_*gli 25 c++ destructor memory-management forward-declaration delete-operator
考虑用来解释一下这个典型的例子并不与前向声明做:
//in Handle.h file
class Body;
class Handle
{
public:
Handle();
~Handle() {delete impl_;}
//....
private:
Body *impl_;
};
//---------------------------------------
//in Handle.cpp file
#include "Handle.h"
class Body
{
//Non-trivial destructor here
public:
~Body () {//Do a lot of things...}
};
Handle::Handle () : impl_(new Body) {}
//---------------------------------------
//in Handle_user.cpp client code:
#include "Handle.h"
//... in some function...
{
Handle handleObj;
//Do smtg with handleObj...
//handleObj now reaches end-of-life, and BUM: Undefined behaviour
}
Run Code Online (Sandbox Code Playgroud)
我从标准中了解到这个案例正朝向UB,因为Body的析构函数是非常重要的.我想要了解的是这个的根本原因.
我的意思是,问题似乎是由Handle的dtor内联的事实"触发",因此编译器执行类似下面的"内联扩展"(这里几乎是伪代码).
inline Handle::~Handle()
{
impl_->~Body();
operator delete (impl_);
}
Run Code Online (Sandbox Code Playgroud)
在Handle_user.cppHandle实例被销毁的所有翻译单元中(仅在这种情况下),对吗?我只是无法理解这一点:好的,当生成上面的内联扩展时,编译器没有Body类的完整定义,但是为什么它不能简单地让链接器解决这个impl_->~Body()问题,所以让它调用Body的析构函数在其实现文件中实际定义的函数?
换句话说:我理解在Handle破坏时,编译器甚至不知道Body是否存在(非平凡的)析构函数,但为什么它不能像往常那样执行,那就是留下一个链接器填充的"占位符",如果该函数真的不可用,最终会有一个链接器"未解析的外部"?
我在这里错过了一些大事(在那种情况下抱歉这个愚蠢的问题)?如果情况并非如此,我只是想了解这背后的理由.
Ste*_*sop 27
要结合几个答案并添加我自己的答案,没有类定义,调用代码不知道:
delete为课程超载.由于某些或所有这些原因,您不能在不完整类型上调用任何成员函数(另外一个不适用于析构函数的成员函数 - 您不会知道参数或返回类型).析构函数也不例外.所以当你说"为什么它不能像往常那样做?"时,我不确定你的意思.
正如您所知,解决方案是Handle在TU中定义析构函数,该析构函数具有定义的Body相同位置,您定义Handle调用函数或使用其数据成员的每个其他成员函数Body.然后在delete impl_;编译点,所有信息都可用于发出该调用的代码.
请注意,标准实际上是,5.3.5/5:
如果被删除的对象在删除时具有不完整的类类型,并且完整的类具有非平凡的析构函数或释放函数,则行为是未定义的.
我认为这样你就可以删除一个不完整的POD类型,就像free在C中一样.但是如果你尝试的话,g ++会给你一个非常严厉的警告.
调用虚方法或非虚方法是两回事.
如果调用非虚方法,编译器必须生成执行此操作的代码:
由于我们讨论的是析构函数,因此没有任何参数可以放在堆栈上,因此看起来我们可以简单地执行调用并告诉链接器解析调用.不需要原型.
但是,调用虚方法完全不同:
这完全不同,因此编译器必须知道您是在调用虚方法还是非虚方法.
第二个重要的事情是编译器需要知道在vtable中找到虚方法的位置.为此,它还需要具有类的完整定义.