非常に一般的なケース:コンストラクターから純粋仮想メソッドを呼び出そうとしています...
コンストラクター
struct Interface
{
Interface();
virtual void logInit() const = 0;
};
struct Concrete: Interface()
{
virtual void logInit() const { std::cout << "Concrete" << std::endl; }
};
ここで、次の実装を想定します。Interface()
Interface::Interface() {}
その後、すべてがうまくいきます:
Concrete myConcrete;
myConcrete.pure(); // outputs "Concrete"
コンストラクターの後でpureと呼ぶのはとても面倒ですが、コードを正しく因数分解する方がよいでしょうか?
Interface::Interface() { this->logInit(); } // DON'T DO THAT, REALLY ;)
そうすれば一行でできます!!
Concrete myConcrete; // CRASHES VIOLENTLY
なんで ?
オブジェクトはボトムアップで構築されているためです。それを見てみましょう。
クラスを構築するための指示Concrete
(大まかに)
(もちろん)十分なメモリと_vtableにも十分なメモリを割り当てます(仮想関数ごとに1つの関数ポインタ、通常は宣言された順序で、左端のベースから開始します)
コンストラクターを呼び出すConcrete
(表示されないコード)
a>コンストラクターを呼び出しInterface
ます。コンストラクターは_vtableをそのポインターで初期化します
b>Interface
コンストラクターの本体を呼び出す(あなたはそれを書いた)
c>これらのメソッドの_vtable内のポインターをオーバーライドする具体的なオーバーライド
d>Concrete
コンストラクターの本体を呼び出す(あなたはそれを書いた)
だから問題は何ですか ?さて、見てb>
注文してくださいc>
;)
コンストラクター内からメソッドを呼び出すと、virtual
期待したことは実行されません。ポインタを検索するために_vtableに移動しますが、_vtable
はまだ完全には初期化されていません。したがって、重要なことはすべて、次の効果です。
D() { this->call(); }
実際には:
D() { this->D::call(); }
コンストラクター内から仮想メソッドを呼び出す場合、構築されるオブジェクトの完全な動的タイプではなく、現在のコンストラクターの静的タイプが呼び出されます。
私のInterface
/のConcrete
例では、これはInterface
型を意味し、メソッドは仮想純粋であるため、_vtableは実際のポインターを保持しません(たとえば、コンパイラーがデバッグ値をセットアップするのに十分な友好的である場合、0x0または0x01)。
デストラクタ
偶然にも、デストラクタのケースを調べてみましょう;)
struct Interface { ~Interface(); virtual void logClose() const = 0; }
Interface::~Interface() { this->logClose(); }
struct Concrete { ~Concrete(); virtual void logClose() const; char* m_data; }
Concrete::~Concrete() { delete[] m_data; } // It's all about being clean
void Concrete::logClose()
{
std::cout << "Concrete refering to " << m_data << std::endl;
}
では、破壊するとどうなりますか?_vtableはうまく機能し、実際のランタイムタイプが呼び出されます...ただし、ここで意味するのは未定義の動作です。m_data
削除された後、Interface
デストラクタが呼び出される前に何が起こったのか誰が知っているのでしょうか。私はしません ;)
結論
コンストラクタまたはデストラクタ内から仮想メソッドを呼び出さないでください。
そうでない場合は、メモリの破損、頑張ってください;)