squirrel_code @ ウィキ

pImpl × 継承

最終更新:

squirrel_code

- view
管理者のみ編集可

pImple × 継承

last update: 2010/12/28 (Tue)

さて,この記事のテーマとは pImpl と継承でどちらが○めでどちらが○けか,ではない!

なんてタイトルをつけやがる….期待させて申し訳ないが(それも違う)pImpl イディオムとは,ヘッダファイルの依存を減らしたいときなどに使う.簡単なコードは以下の通り.

// pImpl.h
// インターフェイス
class Inteface
{
    class Impl;
    Impl* pImpl;
public:
    Inteface() { pImpl = new Impl(); }
    ~Interface() { delete pImpl; }
    void method();
};

// pImpl.cpp
#include "pImpl.h"
// インターフェイスの実装
class Interface::Impl
{
public:
    Impl() {}
    ~Impl() {}
    void method();
};
Interface::method() { pImpl->method(); }
Interface::Impl::method()
    { // method() の実装 }

Interface クラスの実装に使うヘッダファイル群は interface.cpp でインクルードする.これらのヘッダファイル群は Interface クラスを使う際(つまり interface.h のみインクルードする場合)は読み込まれないから,結果としてコンパイル時間の短縮などの効果が得られる.

pImplの継承

上記のクラスを継承したクラスを作るときはどうすればよいだろうか.とりあえず素直に双方継承してみる.

// pImpl.h
// インターフェイス
class Interface
{
private:
    class Impl;
    Impl* pImpl;
public:
    Interface() { pImpl = new Impl(); }
    virtual ~Interface() { delete pImpl; }
    virtual void method();
};

// 派生インターフェイス
class DerivedInterface : public Interface
{
private:
    class Impl;
    Impl* pImpl;
public:
    DerivedInterface() : Interface() { pImpl = new Impl(); }
    ~DerivedInterface() { delete pImpl; }
    void method2();
};

// pImpl.cpp
#include "pImpl.h"
// インターフェイスの実装
class Interface::Impl
{
public:
    Impl() {}
    ~Impl() {}
    void method();
};
Interface::method() { pImpl->method(); }
void Interface::Impl::method()
    { // method() の実装 }

// 派生インターフェイスの実装
class DeriveInterface::Impl : public Interface::Impl
{
public:
    Impl() : Interface::Impl() {}
    ~Impl() {}
    void method2();
};
// DerivedInterface::method() { pImpl->method(); }
DerivedInterface::method2() { pImpl->method2(); }
void DerivedInterface::Impl::method2()
    { // method2 の実装 }

いきなり問題点満載である^^.継承したはずなのに Interface::Impl クラスと DerivedInterface::Impl クラスの双方が実体化され,method() と method2() で呼ばれるインスタンスが違う.コメントアウトした行を入れれば双方 DerivedInterface::Impl クラスのインスタンスが呼ばれるが,いずれにせよ Interface::Impl クラスが無駄に生成される.インターフェイスクラスの継承関係をはずせばいいのだが,話はそう単純にはいかない.そもそも Interface クラスとポリモーフィックに使えることを期待して DerivedInterface クラスを作ったのだから,この public な継承関係は必須である.直すとしたらここではない.

ならば DerivedInterface のコンストラクタで Interface::Impl の実体化をキャンセルするか?それだと Interface クラスの中身が複雑になりそうである. Interface は Interface なのだから(意味不明^^)ここの中身はシンプルにいきたいところである.

では Interface::pImpl に DerivedInterface::Impl を放り込むか?その場合 DerivedInterface::method2() のコールでダウンキャストしなくてはならない.method 1つならたいしたことは無いが,要はクラスの外側から DerivedInterface::Impl へのアクセスが常にダウンキャスト経由になる.そんな設計はやめたほうがマシである(と個人的には思う)

それを言うなら,そもそも pImpl なクラスを継承する設計自体がおかしいと怒られるかもしれない.それはその通りなんだが,それでもそういう設計が自然な場合もあったりする.例えばツリー状のデータ構造を扱うライブラリを作るときなどで,多くの種類の葉に対して共通の機能を持たせたい場合などが思いつく.

pImpl の継承(その2)

いくぶんマシ(と個人的に感じる)設計は以下の通り.

// pImpl.h
// インターフェイス
class Interface
{
public:
    virtual ~Interface() {}
    virtual void method() = 0;
};

// インターフェイスのファクトリ
class InterfaceFactory
{
public:
    Interface* create();
};

// 派生インターフェイス
class DerivedInterface : virtual public Interface
{
public:
    virtual ~DerivedInterface() {}
    virtual void method2() = 0;
};

// 派生インターフェイスのファクトリ
class DerivedInterfaceFactory
{
public:
    DerivedInterface* create();
};

// pImpl.cpp
#include "pImpl.h"
// インターフェイスの実装
class InterfaceImpl : virtual public Interface
{
public:
    InterfaceImpl() {}
    virtual ~InterfaceImpl() {}
    void method();
};
void IntefaceImpl::method()
    { // method() の実装 }

// 派生インターフェイスの実装
class DerivedInterfaceImpl
    : public InterfaceImpl, public DerivedInterface
{
public:
    DerivedInterfaceImpl() : InterfaceImpl() {}
    ~DerivedInterfaceImpl() {};
    void method() 
        { InterfaceImpl::method(); }
    void method2();
};
void DerivedIntefaceImpl::method2()
    { // method2() の実装 }

// インターフェイスのファクトリの実装
Interface* InterfaceFactory::create()
    { return new InterfaceImpl(); }

// 派生インターフェイスのファクトリの実装
DerivedInterface* DerivedInterfaceFactory::create()
    { return new DerivedInterfaceImpl(); }

出た,菱形継承.継承が2段から3段4段と深くなると菱形ならぬ梯子型継承とも呼ぶべきものになる.確かにややこしいが,virtual をつける箇所がわかってしまえばそれほど怖くは無い・・・と思う.要は,インターフェイスと実装を委譲で実現していたのを継承にしてしまえというやり方.

継承にしたため,インターフェイスクラスは仮想クラスになる.そこで生成だけはファクトリクラスを作ってそこで行う.また, DerivedInterfaceImpl クラスで method() をオーバーライドしておかないと,コンパイラに怒られる.ここだけは確かに美しくないのだが,

#define REDEFMETHOD(RETTYPE, METHOD, INTERFACE) \
    RETTYPE METHOD { return INTERFACE::METHOD; }

みたいなマクロにしてしまう手もある.
もはや pImpl じゃないじゃんとかツっこんじゃダメ^^

&trackback()

参考



コメント

  • コメントの投稿テスト -- (tossy_squirrel) 2010-12-29 03:35:18
名前:
コメント:

すべてのコメントを見る


関連ページ



関連ブログ

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