考虑这个用于解释前向声明不能做什么的经典示例:
//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
}
我从标准中了解到,由于 Body 的析构函数并非微不足道,因此该案例将走向 UB。我试图理解的是真正的根本原因。
我的意思是,这个问题似乎是由 Handle 的 dtor 是内联的事实“触发”的,因此编译器会执行类似于以下“内联扩展”的操作(这里几乎是伪代码)。
inline Handle::~Handle()
{
impl_->~Body();
operator delete (impl_);
}
在所有翻译单元(仅Handle_user.cpp
在这种情况下)中,Handle 实例都会被销毁,对吧?我只是无法理解:好的,当生成上述内联扩展时,编译器没有 Body 类的完整定义,但是为什么它不能简单地让链接器解析该impl_->~Body()
事物,所以让它调用 Body 的析构函数实际在其实现文件中定义的函数?
换句话说:我知道在 Handle 销毁时,编译器甚至不知道 Body 是否存在(非平凡的)析构函数,但是为什么它不能像往常一样做,那就是留下一个“占位符”供链接器填写,如果该功能真的不可用,最终有一个链接器“未解析的外部”?
我在这里错过了什么大事吗(在那种情况下,对不起这个愚蠢的问题)?如果不是这样,我只是想了解这背后的基本原理。