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