「続 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)
表示オプション
横に並べて表示:
変化行の前後のみ表示: