C ++で基本クラスの仮想デストラクタを宣言することは良い習慣ですが、virtual
インターフェイスとして機能する抽象クラスであっても、デストラクタを宣言することは常に重要ですか?いくつかの理由とその例を教えてください。
7 に答える
インターフェイスにとってはさらに重要です。クラスのユーザーはおそらく、具体的な実装へのポインターではなく、インターフェイスへのポインターを保持します。彼らがそれを削除するとき、デストラクタが非仮想の場合、派生クラスのデストラクタではなく、インターフェイスのデストラクタ (または、指定していない場合はコンパイラが提供するデフォルト) を呼び出します。インスタントメモリリーク。
例えば
class Interface
{
virtual void doSomething() = 0;
};
class Derived : public Interface
{
Derived();
~Derived()
{
// Do some important cleanup...
}
};
void myFunc(void)
{
Interface* p = new Derived();
// The behaviour of the next line is undefined. It probably
// calls Interface::~Interface, not Derived::~Derived
delete p;
}
あなたの質問への答えは、多くの場合、常にではありません。抽象クラスが、それへのポインターに対してクライアントが delete を呼び出すことを禁止している場合 (またはそのドキュメントでそう述べている場合)、仮想デストラクタを宣言しなくてもかまいません。
デストラクタを保護することにより、クライアントがそのポインタに対して delete を呼び出すことを禁止できます。このように動作するため、仮想デストラクタを省略することは完全に安全で合理的です。
最終的には仮想メソッド テーブルがなくなり、そのテーブルへのポインターを介して削除不可にする意図をクライアントに知らせることになるため、そのような場合に仮想メソッドを宣言しない理由が実際にあります。
[この記事の項目 4 を参照してください: http://www.gotw.ca/publications/mill18.htm ]
私はいくつかの調査を行い、あなたの答えを要約しようと決心しました。次の質問は、必要なデストラクタの種類を決定するのに役立ちます。
- あなたのクラスは基本クラスとして使用することを目的としていますか?
- いいえ:クラス*の各オブジェクトでvポインターを回避するために、パブリック非仮想デストラクタを宣言します。
- はい:次の質問を読んでください。
- あなたの基本クラスは抽象ですか?(つまり、仮想の純粋なメソッドはありますか?)
- いいえ:クラス階層を再設計して、基本クラスを抽象化してみてください
- はい:次の質問を読んでください。
- ベースポインタを介した多形削除を許可しますか?
- いいえ:不要な使用を防ぐために、保護された仮想デストラクタを宣言します。
- はい:パブリック仮想デストラクタを宣言します(この場合、オーバーヘッドはありません)。
これがお役に立てば幸いです。
* C ++ではクラスをfinal(つまりサブクラス化不可)としてマークする方法がないことに注意することが重要です。したがって、デストラクタを非仮想およびパブリックとして宣言する場合は、他のプログラマーに対して明示的に警告することを忘れないでください。クラスから派生します。
参照:
- 「S.マイヤーズ。より効果的なC++、アイテム33 Addison-Wesley、1996年。」
- ハーブサッター、Virtuality、2001
- C ++ FAQ、20.7、「デストラクタはいつ仮想化する必要がありますか?」
- もちろん、この質問への答え。
はい、常に重要です。派生クラスは、メモリを割り当てたり、オブジェクトが破棄されたときにクリーンアップする必要がある他のリソースへの参照を保持する場合があります。インターフェイス/抽象クラスに仮想デストラクタを指定しない場合、基本クラス ハンドルを介して派生クラス インスタンスを削除するたびに、派生クラスのデストラクタは呼び出されません。
したがって、メモリリークの可能性を開いています
class IFoo
{
public:
virtual void DoFoo() = 0;
};
class Bar : public IFoo
{
char* dooby = NULL;
public:
virtual void DoFoo() { dooby = new char[10]; }
void ~Bar() { delete [] dooby; }
};
IFoo* baz = new Bar();
baz->DoFoo();
delete baz; // memory leak - dooby isn't deleted
必ずしも必要というわけではありませんが、良い習慣だと思います。それが何をするかというと、基本型のポインターを介して派生オブジェクトを安全に削除できるようにすることです。
たとえば、次のようになります。
Base *p = new Derived;
// use p as you see fit
delete p;
Base
に仮想デストラクタがない場合、オブジェクトを削除しようとするため、形式が正しくありませんBase *
。
それは良い習慣だけではありません。これは、クラス階層のルール #1 です。
- C++ の階層の最も基本的なクラスには、仮想デストラクタが必要です
なぜなのか。典型的な動物のヒエラルキーを考えてみましょう。仮想デストラクタは、他のメソッド呼び出しと同じように仮想ディスパッチを通過します。次の例を見てください。
Animal* pAnimal = GetAnimal();
delete pAnimal;
Animal が抽象クラスであるとします。C++ が呼び出す適切なデストラクタを知る唯一の方法は、仮想メソッド ディスパッチを使用することです。デストラクタが仮想でない場合は、アニマルのデストラクタを呼び出すだけで、派生クラスのオブジェクトは破棄されません。
基本クラスでデストラクタを仮想にする理由は、派生クラスから選択を単純に削除するためです。それらのデストラクタはデフォルトで仮想になります。
答えは簡単です。仮想である必要があります。そうしないと、基本クラスは完全なポリモーフィック クラスにはなりません。
Base *ptr = new Derived();
delete ptr; // Here the call order of destructors: first Derived then Base.
上記の削除を希望しますが、基本クラスのデストラクタが仮想でない場合は、基本クラスのデストラクタのみが呼び出され、派生クラスのすべてのデータは削除されません。