More C++ Idioms/仮想コンストラクタ(Virtual Constructor)
出典: フリー教科書『ウィキブックス(Wikibooks)』
目次 |
[編集]
仮想コンストラクタ(Virtual Constructor)
[編集] 意図
あるオブジェクトのコピーか、新しいオブジェクトを、その具体的な型を知ることなしに生成する。
[編集] 別名
[編集] 動機
クラス階層中のメンバ関数の多態的な呼び出しを使うことは、オブジェクト指向プログラミングのコミュニティではよく知られたことである。 それは、is-a(~は~である) (より現実的には behaves-as-a(~として振る舞う))関係を実装する方法の一つである。 時々クラス階層中の生存期間管理(生成、コピー、破棄)関数を多態的に呼び出すことが便利な場合がある。
C++ は、仮想デストラクタを用いることでオブジェクトの多態的な破棄に(言語組み込みの機能で)対応している。 オブジェクトの生成やコピーに対しては同様の対応は存在しない。 C++ では、オブジェクトの生成には常にその型をコンパイル時に知っている必要がある。 仮想コンストラクタ(Virtual Constructor)イディオムは、C++ で多態的なオブジェクトの生成やコピーを可能とする。
[編集] 解法とサンプルコード
以下のように、仮想コンストラクタの効果は、オブジェクトの生成に create() メンバ関数を用い、コピー生成に clone() メンバ関数を用いることで得られる。
class Employee { public: virtual ~Employee () {} // 多態的破棄の言語組み込み対応 virtual Employee * create () const = 0; // 仮想コンストラクタ(生成) virtual Employee * clone () const = 0; // 仮想コンストラクタ(コピー) }; class Manager : public Employee // "is-a" 関係 { public: Manager (); // デフォルトコンストラクタ Manager (Manager const &); // コピーコンストラクタ ~Manager () {} // デストラクタ Manager * create () const // 仮想コンストラクタ(生成) { return new Manager(); } Manager * clone () const // 仮想コンストラクタ(コピー) { return new Manager (*this); } }; class Programmer : public Employee { /* Manager クラスとほとんど同様 */ }; Employee * duplicate (Employee const & e) { return e.clone(); // 仮想コンストラクタイディオムの使用。 }
Manager クラスは 2 つの純粋仮想関数を実装し、型名(Manager)を用いてその型のオブジェクトを生成する。 duplicate 関数は、どのように仮想コンストラクタイディオムが使用されるかを示している。 duplicate 関数は、実際にはなにを複製しているかを知らない。 (実際には Manager かもしれないし Programmer かもしれない) Employee を複製していることを知っているだけである。 正しいインスタンスを生成する責任は派生クラスに委譲されている。 それゆえ、 Employee を頂点とするクラス階層に将来さらに派生クラスが追加されたとしても、duplicate 関数は変更に対して影響を受けない。
Manager クラスの clone および create メンバ関数の返値の型は Employee ではなく、そのクラス自身(Manager)である。 C++ は、オーバーライドした関数の返値の型を、基本クラス中での関数の返値の型の派生クラスの型にすることができるという自由度を認めている。 この言語機能は、共変の返値型(co-variant return types)として知られている。
リソースの所有権を正しく扱うため、clone() および create() 関数をファクトリ関数とみなし、その返値に対して、リソースの返値(Resource Return)イディオムを用いるべきである。 使用した場合、返値の型は(例えば shared_ptr<Employee> と shared_ptr<Manager> のようになり)、もはや共変の返値型ではなくなりコンパイルに失敗するはずである。 そのような場合、派生クラスの仮想コンストラクタ関数は親クラスでの正確な型を返さなければならない。
#include <tr1/memory> class Employee { public: typedef std::tr1::shared_ptr<Employee> Ptr; virtual ~Employee () {} // 多態的破棄の言語組み込み対応 virtual Ptr create () const = 0; // 仮想コンストラクタ(生成) virtual Ptr clone () const = 0; // 仮想コンストラクタ(コピー) }; class Manager : public Employee // "is-a" 関係 { public: Manager () {} // デフォルトコンストラクタ Manager (Manager const &) {} // コピーコンストラクタ ~Manager () {} Ptr create () const // 仮想コンストラクタ(生成) { return Ptr(new Manager()); } Ptr clone () const // 仮想コンストラクタ(コピー) { return Ptr(new Manager (*this)); } };