2007年09月19日

仮想デストラクタの持つ意味

以下のコードは危険である。

#include<iostream>
using namespace std;

class Base{
  public:
  Base(){cout << "Base::Constructor" << endl;}
  ~Base(){cout << "Base::Destructor" << endl;}
};

class Derived:public Base{
  public:
  Derived(){cout << "Derived::Constructor" << endl;}
  ~Derived(){cout << "Derived::Destructor" << endl;}
};

int main(void)
{
  Base *p = new Derived();
  delete p;
}

実行結果

Base::Constructor
Derived::Constructor
Base::Destructor

基底クラスのオブジェクトとして派生クラスのオブジェクトを生成し代入しているが、delete時に派生クラスのオブジェクトのデストラクタの呼び出しが行われないのである。仮に派生クラスのメンバ変数にポインタ等が含まれており、デストラクタでそのポインタのメモリを開放する記述を行っていた場合、デストラクタが呼び出されずにメモリリークの原因となってしまう。その為、継承の可能性のあるクラス(事実上ほぼ全て)のデストラクタは仮想デストラクタにしておく必要がある。そうすることによって、派生クラスのデストラクタが呼び出され、さらに基底クラスのデストラクタの呼び出しも正常に行われるのである。

#include<iostream>
using namespace std;

class Base{
  public:
  Base(){cout << "Base::Constructor" << endl;}
  virtual ~Base(){cout << "Base::Destructor" << endl;}
};

class Derived:public Base{
  public:
  Derived(){cout << "Derived::Constructor" << endl;}
  virtual ~Derived(){cout << "Derived::Destructor" << endl;}
};

int main(void)
{
  Base *p = new Derived();
  delete p;
}

実行結果

Base::Constructor
Derived::Constructor
Derived::Destructor
Base::Destructor

ちなみに「デストラクタは仮想デストラクタにしておく必要がある」とあるが、逆に継承を望んでいるクラスのデストラクタが仮想デストラクタで無い場合は、そのクラスが継承を想定していない(継承に対しての対策が無い)設計だという可能性も考える必要がある。

NVIイディオムとは

Effective C++を読んでのメモ

NVI(non virtual interface:非仮想インターフェース)イディオムとはデザインパターンのテンプレートメソッドの具体化の一例。クラスに実装したpublicな関数(非仮想関数)からprivateな仮想関数を呼び出す設計。publicな関数がラッパ関数(Wrapper Function)となり、派生クラスで再定義された仮想関数を呼び出す仕組み。派生クラスで不変的な動作を基底クラスのpublicな関数で定義したい場合などに有効。