Arantium Maestum

プログラミング、囲碁、読書の話題

Effective C++勉強メモ: Item 7 Polymorphic Base Classのdestructorはvirtual

なんのこっちゃ。というのはさすがに嘘だが結構頭の中でパースするのが大変な文ではある。

1. Base Classというのは継承を前提としたクラス。

2. Polymorphic Base Classとは、継承した子クラスをベースクラスのインタフェースを通して使用することを前提としたクラス。

例えばこんな流れ:

class BaseClass { // ベースクラスの記述 };

class ChildA: public BaseClass { // 子クラスAの記述 };
class ChildB: public BaseClass { // 子クラスBの記述 };
class ChildC: public BaseClass { // 子クラスCの記述 };

*BaseClass x = factoryGetPointerToNewInstanceOfChildClass();
x.run();

BaseClassクラスから継承した子クラスのいずれかのNewされたインスタンスへのポインタを返すFactory関数の戻り値をBaseClass*型のxに入れて、xを使うときはBaseClassに定義されている関数だけを呼び出す。

3. virtual関数とは継承されたクラスのインスタンスが、ベースクラスのポインタ・インタフェースを通して扱われても、ベースクラス自体のメンバ関数ではなく実際のクラスのメンバ関数の処理が呼び出されるようになる機構。多くの場合、関数ポインタとvirtual function tableなるマップ的なもので実装されている(がそれはコンパイラによる)。

上記の例では、run関数がBaseClassでvirtual宣言されていないと、x.run()はBaseClassの処理を呼んでしまって全然ポリモーフィックじゃなくなる。インターフェースを提供するBaseClassの場合、インターフェースを構成するメンバ関数はvirtualにする。ちなみにベースクラスでvirtual関数を宣言だけして定義を書かない場合、そのベースクラスは継承にだけ使えるAbstract Base Classになる。BaseClassのインスタンスを作成しようとするとリンクエラー。

さて、ここから本題。Polymorphic Base Classのデストラクタがvirtualでないとどうなるか。

{
  *BaseClass x = factoryGetPointerToNewInstanceOfChildClass();
  x.run();
} // ここで未定義!

上記のコードでスコープから出てxが破棄されるときに、デストラクタがvirtualじゃないのでBaseClassのものが呼び出される。これは挙動が未定義。実際にどうなるかはコンパイラ実装に依存するが、BaseClassのメンバデータだけ破棄され、子クラスのデータはメモリに残ったままになる場合が多いらしい。

インターフェースを利用するつもりで継承するためのベースクラスならちゃんとvirtualデストラクタを宣言しよう、ということと、stlのコンテナクラスなどを安易に継承するとこの問題を踏みがちなので気をつけよう(コンテナクラスは継承するのではなく移譲するのが推奨)。