「続 pImpl × 継承」の編集履歴(バックアップ)一覧はこちら

続 pImpl × 継承」(2010/12/28 (火) 17:06:43) の最新版変更点

追加された行は緑色になります。

削除された行は赤色になります。

* 続 pImpl と継承 #right{last update: &update(format=Y/m/d (D))} [[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++>http://www002.upp.so-net.ne.jp/ys_oota/effec/chapter6.htm]] の記述を読むと,やっと「そいういうことか」と解った気がした. でも protected 継承がほぼ無視されているのが不憫すぎる^^.いい子なんだからもうちょっと光をあててよ. &trackback() ---- **参考 -[[pImpl Idiom - c2.com>http://c2.com/cgi/wiki?PimplIdiom]] -[[pImplイディオムを語る - ありえるえりあ>http://dev.ariel-networks.com/Members/matsuyama/pimpl30a430a330aa30e030928a9e308b]] -[[Effective C++ 入門>http://www002.upp.so-net.ne.jp/ys_oota/effec/chapter6.htm]] ---- **コメント #comment(title_name=name,title_msg=comment) ---- **関連ページ #related() ---- **関連ブログ #blogsearch(pImpl)
* 続 pImpl × 継承 #right{last update: &update(format=Y/m/d (D))} [[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++>http://www002.upp.so-net.ne.jp/ys_oota/effec/chapter6.htm]] の記述を読むと,やっと「そいういうことか」と解った気がした. でも protected 継承がほぼ無視されているのが不憫すぎる^^.いい子なんだからもうちょっと光をあててよ. &trackback() ---- **参考 -[[pImpl Idiom - c2.com>http://c2.com/cgi/wiki?PimplIdiom]] -[[pImplイディオムを語る - ありえるえりあ>http://dev.ariel-networks.com/Members/matsuyama/pimpl30a430a330aa30e030928a9e308b]] -[[Effective C++ 入門>http://www002.upp.so-net.ne.jp/ys_oota/effec/chapter6.htm]] ---- **コメント #comment_num2(size=40,vsize=10,num=20,logpage=コメントログ) ---- **関連ページ #related() ---- **関連ブログ #blogsearch(pImpl)

表示オプション

横に並べて表示:
変化行の前後のみ表示:
記事メニュー
人気記事ランキング
目安箱バナー