🐈

派生クラス破棄時に基底クラスのデストラクタは呼ぶ必要ない

2022/10/24に公開約1,000字2件のコメント

概要

C++で基底クラスを継承させた派生クラスを実装した際、この派生クラスのデストラクタ内で基底クラスのデストラクタも呼び出しが必要ではないか?と疑問が湧いたため継承した場合のデストラクタの動作仕様を調査した。

結論

派生クラスのデストラクタコール時、基底クラスのデストラクタも自動で呼ばれる。そのため、派生クラス破棄時に基底クラスのデストラクタは呼ぶ必要はない。

詳細

例えば以下のように基底クラスとしてBase、その派生クラスとしてHoge、そしてHogeクラスを使用するmain関数を用意して検証してみた。

class Base
{
public:
    Base(){}
    virtual ~Base(){ std::cout << "Call Base Destractor" << "\n"; }
    void Run() { std::cout << "Call Base Run" << "\n"; }
};

class Hoge : public Base
{
public:
    Hoge(){}
    ~Hoge() override { std::cout << "Call Hoge Destractor" << "\n"; }
    void Run() { std::cout << "Call Hoge Run" << "\n"; }
};

int main()
{
    Hoge* instance = new Hoge();
    instance->Run();
    delete instance;
    return 0;
}

このように実装した場合、プログラム実行で以下のように出力される。

Call Hoge Run
Call Hoge Destractor
Call Base Destractor

つまり派生クラスのデストラクタが呼ばれた後、自動で基底クラスのデストラクタもコールされるということだ。

<補足>
基底クラスのデストラクタにvirtualを付けていない場合、子クラスのポインタを親クラスのポインタにキャストして使用したりすると上手くデストラクタがコールされないことがあるらしい。自分の環境では付けても付けなくても動作変わらなかったが、コンパイラ依存かも。

Discussion

基底クラスのデストラクタにvirtualを付けていない場合、子クラスのポインタを親クラスのポインタにキャストして使用したりすると上手くデストラクタがコールされないことがあるらしい。

C++ の言語仕様(の草案)[1]や有志のリファレンスサイト(英語版, 日本語版)などによれば、 delete 式において実際は派生クラスのオブジェクトを指しているような基底クラスへのポインタを削除する場合にはその基底クラスのデストラクタは仮想デストラクタである必要があり、そうでなければ動作は未定義である、とされています。

各所理系における実行例を見てみると、基底クラスのデストラクタが仮想デストラクタでない場合には派生クラスのデストラクタが呼ばれないことが分かります。
これでは派生クラスで行うべき後処理を適切に行えないことになるので、意図しない挙動となる可能性が高いです。

未定義動作を避ける意味でも、ポインタをキャストして使うことを想定している限り基底クラスのデストラクタに virtual は付けておくべきだと思われます。

脚注
  1. N4659 8.3.5.3 ↩︎

コメントありがとうございます。
リファレンスサイト初めて見ました、参考にさせていただきます。

ログインするとコメントできます