前方宣言で何をしてはいけないかを説明するために使用されるこの古典的な例を考えてみましょう:
//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に向かっていることを標準から理解しています。私が理解しようとしているのは、これの根本的な原因です。
つまり、ハンドルの dtor がインラインであるという事実によって問題が「トリガー」されているように見えるため、コンパイラは次の「インライン展開」のようなことを行います (ここではほぼ疑似コード)。
inline Handle::~Handle()
{
impl_->~Body();
operator delete (impl_);
}
Handle インスタンスが破棄されるすべての翻訳単位 (Handle_user.cpp
この場合のみ) で、そうですか? 私はこれを理解できません:わかりました、上記のインライン展開を生成するとき、コンパイラーは Body クラスの完全な定義を持っていませんが、なぜ単純にリンカにimpl_->~Body()
物事を解決させて、Body のデストラクタを呼び出させることができないのでしょうか?実装ファイルで実際に定義されている関数?
言い換えれば、Handle 破壊の時点で、コンパイラは Body に対して (自明ではない) デストラクタが存在するかどうかさえわからないことを理解していますが、なぜいつものようにできないのでしょうか。リンカーが埋めるための「プレースホルダー」であり、その機能が実際に利用できない場合、最終的にリンカーは「未解決の外部」になりますか?
ここで何か大きなものを見逃していますか (その場合、愚かな質問で申し訳ありません)。そうでない場合は、この背後にある理論的根拠を理解したいと思っています。