仮想デストラクタはどのように機能しますか?
-
01-10-2019 - |
質問
数時間前に、私はメモリリークの問題をいじりましたが、仮想デストラクタが間違っているという基本的なものが本当に得られたことがわかりました!クラスのデザインを説明させてください。
class Base
{
virtual push_elements()
{}
};
class Derived:public Base
{
vector<int> x;
public:
void push_elements(){
for(int i=0;i <5;i++)
x.push_back(i);
}
};
void main()
{
Base* b = new Derived();
b->push_elements();
delete b;
}
Bounds Checkerツールは、派生クラスベクトルのメモリリークを報告しました。そして、私は破壊者が仮想ではなく、派生クラスデストラクタが呼ばれていないことを理解しました。そして、Destructor Virtualを作ったとき、それは驚くべきことに修正されました。派生クラスデストラクタが呼び出されていなくても、ベクトルは自動的に扱われませんか?それはBoundscheckerツールの癖ですか、それとも仮想デストラクタの私の理解が間違っていますか?
解決
ベースクラスに仮想デストラクタがない場合、ベースクラスのポインターを介して派生クラスオブジェクトを削除すると、未定義の動作が生じます。
あなたが観察したこと(オブジェクトの派生部分が破壊されることはなく、したがってそのメンバーが扱われることはない)はおそらく多くの可能な行動の中で最も一般的であり、あなたのデストラクタが確実になることが重要である理由の良い例です仮想この方法で多型を使用する場合。
他のヒント
基本クラスに仮想デストラクタがない場合、コードの結果は未定義の動作であり、必ずしも間違ったデストラクタが呼び出されるわけではありません。これはおそらく、Boundscheckerが診断しているものです。
これは技術的には定義されていませんが、診断するために最も一般的な障害方法を知る必要があります。その一般的な障害方法は、間違った破壊者を呼び出すことです。確かに2つの実装しか使用していませんが、他の方法で失敗する実装はわかりません。
これが起こる理由は、非仮想的なメンバー関数をオーバーライドし、ベースポインターを介してそれを呼び出すときに「間違った」関数が呼び出されるのと同じ理由です。
破壊者が仮想ではない場合、ベースデストラクタが呼び出されます。ベースデストラクタは、ベースオブジェクトをクリーンアップして仕上げます。ベースオブジェクトデストラクタが導出されたオブジェクトについて知る方法はありません。それは派生デストラクタと呼ばれる必要があり、それを行う方法は、他の機能と同様に、Destructor Virtualを作成することです。
C ++ FAQ Liteから:「私のデストラクタはいつ仮想になるべきですか?」それを読んで ここ. 。 (C ++ FAQ Liteは、ところで、C ++に関連するすべての質問の優れたソースです)。
C ++で、a 些細な破壊者 再帰的に定義された概念です。これは、クラスのすべてのメンバー(およびすべてのベースクラス)が些細なデストラクタを持っているときに、コンパイラがあなたのために書いた破壊者です。 (Trivial Constructorと呼ばれる同様の概念があります。)
自明でないデストラクタを持つオブジェクトがオブジェクトに含まれている場合( vector
あなたの例で)、次に外側のオブジェクトのデストラクタ(あなたのように Derived
)inはもはや些細なことではありません。 Destructorを書きませんでしたが、C ++コンパイラは、デストラクタを持つメンバーのデストラクタを呼び出すデストラクタを自動的に書きました。
したがって、何も書かなかったとしても、非仮想的なデストラクタを書くという警告はまだ適用されます。
C#から来ている場合、なぜVectorが自動的に脱アロークされないのか不思議に思っていました。しかし、C ++では、Microsoft Manged ExtesionsをC ++(C ++/CLI)に使用しない限り、自動メモリ管理は利用できません。
仮想クラスにはデストラクタがないため、派生クラスオブジェクトが解放されることはなく、派生クラスのベクトルデータメンバーに割り当てられたメモリをリークします。
Destructorは、クラス名と同じ名前であり、Tilde Sign(〜)が前にあるクラスのメンバー関数です。 Destructorは、オブジェクトが範囲外になったときにクラスのオブジェクトを破壊するために使用されます。または、クラスの破壊のすべてのクリーンアップがDestructorで行われると言えます。オブジェクトが範囲外に出ると、クラス内のオブジェクトの構築中にすべてのメモリが割り当てられます(またはメモリリリース)。
例で詳細をご覧ください Boundscheck