デストラクタ(もちろんコンストラクタ)と他のメンバー関数の違いは、通常のメンバー関数の派生クラスに本体がある場合、派生クラスのバージョンのみが実行されることです。デストラクタの場合、派生クラスと基本クラスバージョンの両方が実行されますか?
デストラクタ(おそらく仮想)およびコンストラクタの場合に正確に何が起こるかを知ることは、最も派生したクラスオブジェクトが削除された場合でも、すべての基本クラスに対して呼び出されることを知っておくと便利です。
前もって感謝します!
標準は言う
デストラクタの本体を実行し、本体内に割り当てられた自動オブジェクトを破棄した後、クラス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).
}
コンストラクタとデストラクタは、他の通常のメソッドとは異なります。
コンストラクター
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
であり、同じ順序で呼び出されます。
これは仕様です。リソースを解放するには、基本クラスのデストラクタを呼び出す必要があります。経験則では、派生クラスは自身のリソースのみをクリーンアップし、基本クラスはそのままにしてクリーンアップする必要があります。
C++仕様 から:
デストラクタの本体を実行し、本体内に割り当てられた自動オブジェクトを破棄した後、クラスXのデストラクタは、Xの直接メンバーのデストラクタ、Xの直接基本クラスのデストラクタを呼び出し、Xが最も派生したクラス( 12.6.2)、そのデストラクタはXの仮想ベースクラスのデストラクタを呼び出します。すべてのデストラクタは、修飾された名前で参照されているかのように呼び出されます。つまり、派生クラスで仮想オーバーライド可能なデストラクタを無視します。ベースとメンバーは、コンストラクターの完了と逆の順序で破棄されます(12.6.2を参照)。
また、デストラクタは1つしかないため、クラスが呼び出す必要のあるデストラクタについてあいまいさはありません。これは、コンストラクターの場合ではありません。コンストラクターは、アクセス可能なデフォルトコンストラクターがない場合に、どの基本クラスコンストラクターを呼び出すかをプログラマが選択する必要があります。
それがdtorの仕組みだからです。オブジェクトを作成すると、ベースから起動され、最も派生したものに至るまですべてのアクターが呼び出されます。オブジェクトを(正しく)破棄すると、逆のことが起こります。 dtorを仮想化することで違いが生じるのは、ベース型へのポインター(または、かなり珍しいことですが)を介してオブジェクトを破棄する場合です。その場合、代替案は実際には派生したdtorのみが呼び出されるということではありません。むしろ、代替案は単に未定義の動作です。それはたまたま派生したdtorだけを呼び出すという形をとりますが、まったく異なる形をとることもあります。
イゴールが言うように、コンストラクターは基本クラスのために呼び出されなければなりません。呼び出されなかった場合にどうなるかを考えてみましょう。
struct A {
std::string s;
virtual ~A() {}
};
struct B : A {};
A
インスタンスを削除するときにB
のデストラクタが呼び出されない場合、A
はクリーンアップされません。
基本クラスデストラクターは、基本クラスコンストラクターによって割り当てられたリソースのクリーンアップを担当する場合があります。
基本クラスにデフォルトコンストラクター(パラメーターを受け取らない、またはすべてのパラメーターにデフォルトがあるコンストラクター)がある場合、そのコンストラクターは派生インスタンスの構築時に自動的に呼び出されます。
基本クラスにパラメーターを必要とするコンストラクターがある場合、派生クラスコンストラクターの初期化リストで手動で呼び出す必要があります。
デストラクタはパラメータを受け取らないため、派生クラスの削除時に常にベースクラスデストラクタが自動的に呼び出されます。
ポリモーフィズムを使用しており、派生インスタンスがベースクラスポインターによってポイントされている場合、ベースデストラクターが仮想の場合にのみderivedクラスデストラクターが呼び出されます。
オブジェクトが破壊されると、すべてのサブオブジェクトに対してデストラクタが実行されます。これには、包含による再利用と継承による再利用の両方が含まれます。