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
名前:
コメント:

すべてのコメントを見る


関連ページ



関連ブログ

#blogsearch
記事メニュー
目安箱バナー