Code Fix

上級

仮想デストラクタが無いと派生クラスのデストラクタが呼ばれない理由

基底クラスのポインタ経由で派生クラスのオブジェクトをdeleteしたとき、基底クラスのデストラクタが仮想(virtual)でないと、派生クラス側のデストラクタが呼ばれないバグです。

なぜエラーが出ないのか

出力: Base destroyed(Derived destroyedが呼ばれない)
(エラーなし)
C++は何も報告しません。コンパイルも実行も正常に完了するためです
出力: Base destroyed(Derived destroyedが呼ばれない)
実際の挙動 — 期待した結果と食い違っている箇所
見つけ方
エラーが出ないので、出力を目で確かめるか、コードを目で追うしかありません。この種の誤りが最も発見が遅れます

このエラーが出る典型パターン

パターン1

 1  #include <iostream>
 2  class Base {
 3  public:
 4      ~Base() { std::cout << "Base destroyed" << std::endl; }
 5  };
 6  class Derived : public Base {
 7  public:
 8      ~Derived() { std::cout << "Derived destroyed" << std::endl; }
 9  };
10  int main() {
11      Base*    b = new Derived();
            ^
12      delete b;
13      return 0;
14  }
出力: Base destroyed(Derived destroyedが呼ばれない)期待: Derived destroyed → Base destroyed

Baseのデストラクタがvirtualでないため、Base*経由でのdeleteはBaseのデストラクタしか呼びません。ポインタの型をDerived*にすれば正しい型のデストラクタから呼ばれます(本来の解決策はBaseのデストラクタをvirtualにすることです)。

直し方: Base*Derived* にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  #include <iostream>
 2  class Shape {
 3  public:
 4      ~Shape() { std::cout << "Shape destroyed" << std::endl; }
 5  };
 6  class Circle : public Shape {
 7  public:
 8      ~Circle() { std::cout << "Circle destroyed" << std::endl; }
 9  };
10  int main() {
11      Shape*  c = new Circle();
           ^
12      delete c;
13      return 0;
14  }
出力: Shape destroyed(Circle destroyedが呼ばれない)期待: Circle destroyed → Shape destroyed

仮想関数として宣言されていないメンバ関数の呼び出しは、実行時の実際の型ではなく、ポインタの静的な型を基準に決まります。デストラクタも例外ではありません。

直し方: Shape*Circle* にします。

この問題を解いてみる →

パターン3

 1  #include <iostream>
 2  class Resource {
 3  public:
 4      ~Resource() { std::cout << "Resource destroyed" << std::endl; }
 5  };
 6  class FileHandle : public Resource {
 7  public:
 8      ~FileHandle() { std::cout << "FileHandle destroyed" << std::endl; }
 9  };
10  int main() {
11      Resource*   f = new FileHandle();
             ^
12      delete f;
13      return 0;
14  }
出力: Resource destroyed(FileHandle destroyedが呼ばれない)期待: FileHandle destroyed → Resource destroyed

「deleteすればそのオブジェクトの型に応じたデストラクタが正しく呼ばれる」というのは誤解です。実際に呼ばれるファイルクローズ処理などが仮想デストラクタなしでは実行されず、リソースリークの原因になります。

直し方: Resource*FileHandle* にします。

この問題を解いてみる →

よくある誤解

「deleteすればそのオブジェクトの型に応じたデストラクタが正しく呼ばれる」というのは誤解です。仮想関数として宣言されていないメンバ関数の呼び出しは、実行時の実際の型ではなく、ポインタの静的な型(ここではBase*)を基準に決まります。デストラクタも例外ではありません。

まとめ

仮想デストラクタが無いと派生クラスのデストラクタが呼ばれない理由は上級でつまずきやすい項目です。上の3パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)