squirrel_code @ ウィキ
続 pImpl × 継承
最終更新:
squirrel_code
-
view
続 pImpl × 継承
last update: 2010/12/28 (Tue)
pImpl × 継承 の続き
その2では,委譲を継承に置き換えて pImpl の継承を実現した.他のやり方はないだろうか.
pImpl の継承(その3)
ここで pImpl イディオムのクラス間関係をまとめてみる.
関係 | その1 | その2 | 候補1 | 候補2 |
インターフェイス間 | 継承 | 継承 | 継承 | 継承 |
インターフェイス-実装間 | 委譲 | 継承 | 委譲 | 継承 |
実装間 | 継承 | 継承 | 委譲 | 委譲 |
結果 | × | ○ | ×? | ○? |
よく考えると,ここで必須なのはインターフェイス間の継承だけである.他の関係は実は継承でなくてもよい.しかし,その1での考察から,候補1はまずそうである.そこで候補2を考える.
// pImpl.h // インターフェイス class Interface { public: virtual ~Interface() {} virtual void method() = 0; }; // インターフェイスのファクトリ class InterfaceFactory { public: Interface* create(); }; // 派生インターフェイス class DerivedInterface : public Interface { public: virtual ~DerivedInterface() {} virtual void method2() = 0; }; // 派生インターフェイスのファクトリ class DerivedInterfaceFactory { public: DerivedInterface* create(); };
// pImpl.cpp #include "pImpl.h" // インターフェイスの実装 class InterfaceImpl : public Interface { public: InterfaceImpl() {} virtual ~InterfaceImpl() {} void method(); }; void InterfaceImpl::method() { // method() の実装 } // インターフェイスのファクトリの実装 Interface* InterfaceFactory::create() { return new InterfaceImpl(); } // 派生インターフェイスの実装 class DerivedInterfaceImpl : public DerivedInterface { InterfaceImpl* impl; public: DerivedInterfaceImpl() { InterfaceFactory f; impl = f.create(); } ~DerivedInterfaceImpl() { delete impl; }; void method() { return impl->method(); } void method2(); }; void DerivedIntefaceImpl::method2() { // method2() の実装 } // 派生インターフェイスのファクトリの実装 DerivedInterface* DerivedInterfaceFactory::create() { return new DerivedInterfaceImpl(); }
おお,これはきれいだ.菱形継承どころか多重継承すら無い.実装の再利用もできている.しかしながら問題点もあって,一番気になるのは DerivedInterfaceImpl クラスが InterfaceImpl クラスの公開メンバにしかアクセスできない点だ.実装クラスなんだから全部公開にしておくとか,friend を使うとかでもよいが,ちょっとイマイチである.
pImpl の継承(その4)
ここで,C++ で委譲を実現するのに,単にポインタを持つ以外の方法があった.普段めったに使わないが, private/protected 継承である.試しに「その3」を private 継承で書き直してみる.
// pImpl.h // インターフェイス class Interface { public: virtual ~Interface() {} virtual void method() = 0; }; // インターフェイスのファクトリ class InterfaceFactory { public: Interface* create(); }; // 派生インターフェイス class DerivedInterface : public Interface { public: virtual ~DerivedInterface() {} virtual void method2() = 0; }; // 派生インターフェイスのファクトリ class DerivedInterfaceFactory { public: DerivedInterface* create(); };
// pImpl.cpp #include "pImpl.h" // インターフェイスの実装 class InterfaceImpl : public Interface { public: InterfaceImpl() {} virtual ~InterfaceImpl() {} void method(); }; void IntefaceImpl::method() { // method() の実装 } // 派生インターフェイスの実装 class DerivedInterfaceImpl : public DerivedInterface, : private InterfaceImpl { public: DerivedInterfaceImpl() : InterfaceImpl() {} ~DerivedInterfaceImpl() {} void method() { return InterfaceImpl::method(); } void method2(); }; void DerivedIntefaceImpl::method2() { // method2() の実装 } // インターフェイスのファクトリの実装 Interface* InterfaceFactory::create() { return new InterfaceImpl(); } // 派生インターフェイスのファクトリの実装 DerivedInterface* DerivedInterfaceFactory::create() { return new DerivedInterfaceImpl(); }
おおー!感動,である.DerivedInterfaceImpl の定義がずっと自然になった. InterfaceImpl クラスの非公開メンバにも自由にアクセスできる.素晴らしい.このままでは DerivedInterfaceImpl からさらに継承する場合 InterfaceImpl の非公開メンバにアクセスできなくなるが,そのような場合は protected 継承の出番である.というか protected 継承の使いどころをはじめて見た気がする.
また,Interface が抽象的なインターフェイスであり,インスタンス化されうるのは Interface から派生した様々なクラスのみであるような場合(ただし Interface で機能の一部を実装したい場合になるが)は,InterfaceImpl クラスは Interface クラスを継承する必要がない.つまり Interface - DerivedInteface の継承ツリーでインターフェイスとしてのツリーをつくり,実装の再利用は InterfaceImpl - DerivedInterfaceImpl 系の独立したツリーにすることができる.インスタンス化されうるツリーの末端クラスではじめてインターフェイスツリーと実装ツリーをマージするような設計が可能だ.
ここまでのまとめ
pImpl イディオムの目的としてよく言われている「実装を隠す」,あるいは「ヘッダ依存の分離」のためには,「インターフェイスクラスを実装クラスで継承し,ファクトリクラスを作る.インターフェイスに継承関係がある場合,実装クラスは protected 継承する」が筋がよいように思える.
もとの pImpl との違いを考えると,即値での代入やコピーが実現できないが,インターフェイス間の継承を考えている時点でこれは考慮の外である.
実際のオブジェクトの使用場面で即値での代入やコピーが必要なケースと,インターフェイス間の継承が必要なケースを比べると,後者のほうが需要があると思うのだがいかがだろうか.
というか,ここまで考えてから Effective C++ の記述を読むと,やっと「そいういうことか」と解った気がした. でも protected 継承がほぼ無視されているのが不憫すぎる^^.いい子なんだからもうちょっと光をあててよ.
&trackback()
参考
コメント
- コメントの投稿テスト -- (tossy_squirrel) 2010-12-29 03:35:18