【C++学習|初心者向け】C++の謎のクラッシュ「Pure virtual function called」の原因と対策

1. 導入:なぜこのエラーが恐ろしいのか

C++で開発をしていると、プログラムが突然「Pure virtual function called」というメッセージを表示して強制終了することがあります。コンパイルは通っているのに、実行時に突然落ちるため、原因究明に時間がかかる厄介なバグの一つです。このエラーは、C++のオブジェクトの「構築と破棄のプロセス」という深い仕組みを理解していないと遭遇してしまいます。今回は、このエラーの正体と、安全なクラス設計の方法を解説します。

2. 基礎知識:仮想関数とVtableの仕組み

C++の仮想関数(virtual関数)は、実行時に適切な関数を呼び出すために「Vtable(仮想関数テーブル)」という仕組みを利用しています。
Vtableとは、クラスごとに用意された「どの仮想関数を呼び出すか」のリストのようなものです。純粋仮想関数(= 0)を宣言すると、通常はそのVtableのスロットは「未定義」となりますが、C++の標準ライブラリ(libstdc++など)は、この場所に「__cxa_pure_virtual」という特殊なエラーハンドラを埋め込みます。
つまり、このエラーは「派生クラスがまだ構築されていない(あるいは既に破棄された)状態で、その機能を使おうとした」時に発生します。

3. 実装と解決策:コンストラクタからの呼び出しはNG

最も多い原因は、基底クラスのコンストラクタ内で仮想関数を呼ぶことです。
C++では、派生クラスのコンストラクタが動く前に、まず基底クラスのコンストラクタが動きます。このとき、オブジェクトはまだ「基底クラス」として扱われており、派生クラスの機能は存在しません。この状態で仮想関数を呼ぶと、Vtableはまだ派生クラスを指していないため、エラーハンドラにジャンプしてしまいます。

4. サンプルプログラム

以下のコードは、まさにこのエラーを引き起こす典型的な例です。

include

// 基底クラス
class Base {
public:
Base() {
// ここで仮想関数を呼ぶとエラーになる
// なぜなら、派生クラス(Derived)はまだ構築されていないから
call_foo();
}

// 仮想関数
virtual void foo() = 0;

void call_foo() {
foo(); // ここを経由しても結果は同じ
}
};

// 派生クラス
class Derived : public Base {
public:
void foo() override {
std::cout << "派生クラスのfooが呼ばれました" << std::endl; } }; int main() { // Derivedをインスタンス化した瞬間にクラッシュする Derived d; return 0; }

5. 応用・注意点:どう設計すべきか

この問題を回避するための鉄則は、「コンストラクタやデストラクタから仮想関数を直接的・間接的に呼び出さないこと」です。

もし構築時に複雑な初期化を行いたい場合は、以下のパターンを検討してください。

・二段階初期化(Two-phase initialization)
コンストラクタではメモリの確保だけを行い、生成後に明示的な「init()」メソッドを呼び出す設計にします。
・テンプレートメソッドパターン
設計を見直し、仮想関数に依存しない初期化コードを基底クラスに記述します。

また、コンストラクタからさらに別の関数を呼び出すような設計(例:init() -> setup() -> virtual_func())にしていると、静的解析ツールでも検出しにくくなります。クラス設計の段階で、「初期化時に仮想関数を使う必要がないか」を常に意識することが、堅牢なC++プログラムを作る鍵となります。

コメント

タイトルとURLをコピーしました