web-dev-qa-db-ja.com

C ++で抽象クラスの仮想デストラクタを宣言する必要があるのはなぜですか?

C++でベースクラスの仮想デストラクタを宣言することは良い習慣ですが、インターフェイスとして機能する抽象クラスでもvirtualデストラクタを宣言することは常に重要ですか?いくつかの理由と理由を提供してください。

157
Kevin

インターフェイスにとってはさらに重要です。クラスのユーザーは、おそらく具体的な実装へのポインタではなく、インターフェイスへのポインタを保持します。デストラクタが非仮想である場合、それらが削除されると、派生クラスのデストラクタではなく、インターフェイスのデストラクタ(または、指定しない場合はコンパイラが提供するデフォルト)を呼び出します。インスタントメモリリーク。

例えば

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; 
}
183
Airsource Ltd

あなたの質問に対する答えはしばしばありますが、常にではありません。抽象クラスがクライアントへのポインタでdeleteを呼び出すことを禁止している場合(またはドキュメントでそう言っている場合)、仮想デストラクタを宣言しないことは自由です。

デストラクタを保護することにより、クライアントへのポインタでdeleteを呼び出すことを禁止できます。このように動作するため、仮想デストラクタを省略することは完全に安全で合理的​​です。

最終的には仮想メソッドテーブルがなくなり、クライアントへのポインタを介してそれを削除不可にすることをクライアントに通知することになります。そのため、これらの場合は仮想と宣言しない理由があります。

[この記事のアイテム4を参照してください: http://www.gotw.ca/publications/mill18.htm ]

私はいくつかの研究を行い、あなたの答えを要約しようとすることにしました。次の質問は、必要なデストラクタの種類を決定するのに役立ちます。

  1. あなたのクラスは基本クラスとして使用されることを意図していますか?
    • いいえ:クラスの各オブジェクトのvポインターを回避するために、パブリック非仮想デストラクターを宣言します *
    • はい:次の質問を読んでください。
  2. 基本クラスは抽象的ですか? (つまり、仮想純粋メソッドはありますか?)
    • いいえ:クラス階層を再設計して、基本クラスを抽象化してください
    • はい:次の質問を読んでください。
  3. ベースポインタを介して多態的な削除を許可しますか?
    • いいえ:保護された仮想デストラクターを宣言して、不要な使用を防ぎます。
    • はい:パブリック仮想デストラクタを宣言します(この場合、オーバーヘッドはありません)。

これがお役に立てば幸いです。

* C++にはクラスをfinal(つまり、サブクラス化不可)としてマークする方法がないことに注意することが重要です。したがって、デストラクタを非仮想およびパブリックとして宣言する場合は、派生することを同僚のプログラマに明示的に警告することを忘れないでくださいあなたのクラスから。

参照:

23
David Alfonso

はい、それは常に重要です。派生クラスはメモリを割り当てたり、オブジェクトが破棄されたときにクリーンアップする必要がある他のリソースへの参照を保持したりできます。インターフェイス/抽象クラスに仮想デストラクタを与えない場合、ベースクラスハンドルを介して派生クラスインスタンスを削除するたびに、派生クラスのデストラクタは呼び出されません。

したがって、あなたはメモリリークの可能性を開いています

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
7
OJ.

always必須ではありませんが、良い習慣であると思います。それがすることは、派生型のオブジェクトを基本型のポインタを通して安全に削除できるようにすることです。

たとえば、次のとおりです。

Base *p = new Derived;
// use p as you see fit
delete p;

Baseに仮想デストラクタがない場合は、オブジェクトがBase *であるかのようにオブジェクトを削除しようとするため、不正な形式です。

7
Evan Teran

それは良い習慣だけではありません。すべてのクラス階層のルール#1です。

  1. C++の階層の基本クラスには、仮想デストラクタが必要です

理由は次のとおりです。典型的な動物の階層を取ります。仮想デストラクターは、他のメソッド呼び出しと同様に仮想ディスパッチを実行します。次の例をご覧ください。

Animal* pAnimal = GetAnimal();
delete pAnimal;

Animalは抽象クラスであると仮定します。 C++が呼び出す適切なデストラクタを認識する唯一の方法は、仮想メソッドのディスパッチです。デストラクタが仮想でない場合は、単にAnimalのデストラクタを呼び出し、派生クラスのオブジェクトを破棄しません。

基本クラスでデストラクタを仮想化する理由は、派生クラスから選択を単純に削除するためです。デストラクタはデフォルトで仮想になります。

5
JaredPar

答えは簡単です。仮想である必要があります。そうでなければ、基本クラスは完全なポリモーフィッククラスではありません。

    Base *ptr = new Derived();
    delete ptr; // Here the call order of destructors: first Derived then Base.

上記の削除をお勧めしますが、基本クラスのデストラクタが仮想ではない場合、基本クラスのデストラクタのみが呼び出され、派生クラスのすべてのデータは削除されないままになります。

3
fatma.ekici