web-dev-qa-db-ja.com

派生クラスオブジェクトが削除されたときに基本クラスデストラクタ(仮想)が呼び出されるのはなぜですか?

デストラクタ(もちろんコンストラクタ)と他のメンバー関数の違いは、通常のメンバー関数の派生クラスに本体がある場合、派生クラスのバージョンのみが実行されることです。デストラクタの場合、派生クラスと基本クラスバージョンの両方が実行されますか?

デストラクタ(おそらく仮想)およびコンストラクタの場合に正確に何が起こるかを知ることは、最も派生したクラスオブジェクトが削除された場合でも、すべての基本クラスに対して呼び出されることを知っておくと便利です。

前もって感謝します!

31
KedarX

標準は言う

デストラクタの本体を実行し、本体内に割り当てられた自動オブジェクトを破棄した後、クラスXのデストラクタは、Xの直接の非バリアントメンバのデストラクタを呼び出します。Xの直接の基底クラスのデストラクタおよびXが最も派生したクラス(12.6.2)のタイプである場合、そのデストラクタはXの仮想ベースクラスのデストラクタを呼び出します。すべてのデストラクタは、修飾された名前で参照されているかのように呼び出されます。つまり、派生クラスで仮想オーバーライド可能なデストラクタを無視します。 ベースとメンバーは、コンストラクターの完了と逆の順序で破棄されます(12.6.2を参照)。デストラクタ内のreturnステートメント(6.6.3)は、呼び出し元に直接戻らない場合があります。呼び出し元に制御を移す前に、メンバーとベースのデストラクタが呼び出されます。配列の要素のデストラクタは、構築の逆順で呼び出されます(12.6を参照)。

また、 [〜#〜] raii [〜#〜] に従って、リソースを適切なオブジェクトの寿命に関連付ける必要があり、リソースを解放するには、それぞれのクラスのデストラクタを呼び出す必要があります。

たとえば、次のコードはメモリをリークします。

 struct Base
 {
       int *p;
        Base():p(new int){}
       ~Base(){ delete p; } //has to be virtual
 };

 struct Derived :Base
 {
       int *d;
       Derived():Base(),d(new int){}
       ~Derived(){delete d;}
 };

 int main()
 {
     Base *base=new Derived();
     //do something

     delete base;   //Oops!! ~Base() gets called(=>Memory Leak).
 }
16
Prasoon Saurav

コンストラクタとデストラクタは、他の通常のメソッドとは異なります。

コンストラクター

  • バーチャルになることはできません
  • 派生クラスでは、明示的に基本クラスのコンストラクターを呼び出します
  • または、ベースクラスコンストラクターを呼び出さない場合は、コンパイラーが呼び出しを挿入します。パラメーターなしで基本コンストラクターを呼び出します。そのようなコンストラクタが存在しない場合、コンパイラエラーが発生します。

struct A {};
struct B : A { B() : A() {} };

// but this works as well because compiler inserts call to A():
struct B : A { B() {} };

// however this does not compile:
struct A { A(int x) {} };
struct B : A { B() {} };

// you need:
struct B : A { B() : A(4) {} };

デストラクタ

  • 基本クラスに仮想デストラクタがある場合、派生クラスでデストラクタを呼び出すと、最も派生したデストラクタが最初に呼び出され、次に構築の逆の順序で残りの派生クラスが呼び出されます。これは、すべてのメモリが適切にクリーニングされたことを確認するためです。最も派生したクラスが最後に呼び出された場合、その時点までに基本クラスがメモリに存在せず、セグメンテーション違反が発生するため、機能しません。

struct C
{
    virtual ~C() { cout << __FUNCTION__ << endl; }
};

struct D : C
{
    virtual ~D() { cout << __FUNCTION__ << endl; }
};

struct E : D
{
    virtual ~E() { cout << __FUNCTION__ << endl; }
};

int main()
{
    C * o = new E();
    delete o;
}

出力:

~E
~D
~C

基本クラスのメソッドがvirtualとしてマークされている場合、継承されたすべてのメソッドも仮想であるため、DおよびEのデストラクタをvirtualとしてマークしなくても、それらは引き続きvirtualであり、同じ順序で呼び出されます。

13
stefanB

これは仕様です。リソースを解放するには、基本クラスのデストラクタを呼び出す必要があります。経験則では、派生クラスは自身のリソースのみをクリーンアップし、基本クラスはそのままにしてクリーンアップする必要があります。

C++仕様 から:

デストラクタの本体を実行し、本体内に割り当てられた自動オブジェクトを破棄した後、クラスXのデストラクタは、Xの直接メンバーのデストラクタ、Xの直接基本クラスのデストラクタを呼び出し、Xが最も派生したクラス( 12.6.2)、そのデストラクタはXの仮想ベースクラスのデストラクタを呼び出します。すべてのデストラクタは、修飾された名前で参照されているかのように呼び出されます。つまり、派生クラスで仮想オーバーライド可能なデストラクタを無視します。ベースとメンバーは、コンストラクターの完了と逆の順序で破棄されます(12.6.2を参照)。

また、デストラクタは1つしかないため、クラスが呼び出す必要のあるデストラクタについてあいまいさはありません。これは、コンストラクターの場合ではありません。コンストラクターは、アクセス可能なデフォルトコンストラクターがない場合に、どの基本クラスコンストラクターを呼び出すかをプログラマが選択する必要があります。

11
Igor Zevaka

それがdtorの仕組みだからです。オブジェクトを作成すると、ベースから起動され、最も派生したものに至るまですべてのアクターが呼び出されます。オブジェクトを(正しく)破棄すると、逆のことが起こります。 dtorを仮想化することで違いが生じるのは、ベース型へのポインター(または、かなり珍しいことですが)を介してオブジェクトを破棄する場合です。その場合、代替案は実際には派生したdtorのみが呼び出されるということではありません。むしろ、代替案は単に未定義の動作です。それはたまたま派生したdtorだけを呼び出すという形をとりますが、まったく異なる形をとることもあります。

2
Jerry Coffin

イゴールが言うように、コンストラクターは基本クラスのために呼び出されなければなりません。呼び出されなかった場合にどうなるかを考えてみましょう。

struct A {
    std::string s;
    virtual ~A() {}
};

struct B : A {};

Aインスタンスを削除するときにBのデストラクタが呼び出されない場合、Aはクリーンアップされません。

2
Georg Fritzsche

基本クラスデストラクターは、基本クラスコンストラクターによって割り当てられたリソースのクリーンアップを担当する場合があります。

基本クラスにデフォルトコンストラクター(パラメーターを受け取らない、またはすべてのパラメーターにデフォルトがあるコンストラクター)がある場合、そのコンストラクターは派生インスタンスの構築時に自動的に呼び出されます。

基本クラスにパラメーターを必要とするコンストラクターがある場合、派生クラスコンストラクターの初期化リストで手動で呼び出す必要があります。

デストラクタはパラメータを受け取らないため、派生クラスの削除時に常にベースクラスデストラクタが自動的に呼び出されます。

ポリモーフィズムを使用しており、派生インスタンスがベースクラスポインターによってポイントされている場合、ベースデストラクターが仮想の場合にのみderivedクラスデストラクターが呼び出されます。

2
Amardeep AC9MF

オブジェクトが破壊されると、すべてのサブオブジェクトに対してデストラクタが実行されます。これには、包含による再利用と継承による再利用の両方が含まれます。

0
Ben Voigt