What is it, naokirin?

PImplイディオム

今回はPImplイディオム。
プログラミング全般に入れるべきか悩んだんですが、これはC++のポインタという概念があってこそのものかなということでカテゴリはC++にしました。

なぜこのタイミングでPImplイディオムを書くかというと、今日あったわんくま同盟の勉強会#名古屋の最後に秋猫さんが「プログラミングの勉強を始めて2年間のまとめ」という発表をしていた中で出てきたからです。

ちなみに私はPImplイディオムというのを知りませんでした。やっぱりC++ができるとは到底言えませんね・・・。どのサイトを見ても「C++の有名なイディオム」と書いてあります。(自分ができないと知っていてもやっぱり知らないことがあるとへこむ・・・orz)

ところで、調べてみたところ、PImplとは"Pointer to Imprementation"のことで、実装からインターフェースを分離し、クラスの実装が変更されてもそのクラスを使用する部分は影響を受けない、つまり再コンパイル不要にするイディオムのことだそうです。

ところでPImplイディオムには別名があり、"Handle Body"ともいうそうです。利用者にとってのクラスインターフェースとなる"Handle"部分と実際の実装部分である"Body"部分に分けるということです。

例として、HandleとなるSampleHandleとBodyとなるSampleBodyというクラスを作ってみたいと思います。

/* sample.h */
class SampleBody;

class SampleHandle
{
public:
    SampleHandle();
    ~SampleHandle();

    SampleHandle(const SampleHandle &);
    SampleHandle& operator=(const SampleHandle &); 

private:
    SampleBody *body;
};
/* sample.cpp */
#include "sample.h"
namespace /* 匿名の名前空間 */
{
    class SampleBody
    {
        SampleBody();
        ~SampleBody();

        friend SampleHandle; /* SampleHandleにのみメンバを公開 */

        /* 内部実装 */
    };
}


このようにすれば、SampleBodyを変更してもSampleHandleを再コンパイルする必要はありません。
しかし例のようにすると、一つ単純に考えても問題があるのが分かります。(実はもう書いてありますが・・・)

SampleHandleが持っているのはSampleBodyのポインタなので、SampleHandleのインスタンスを生成するときにはSampleBodyのインスタンスを生成する必要があり、SampleHandleが破棄される場合はちゃんとSampleBodyも破棄する必要があります。

また次のような場合を考えてみます。

SampleHandle handleA, handleB;
handleA = handleB;


この場合は代入時にちゃんとSampleBodyのインスタンスの世話をしてあげる必要があります。(つまりoperator=とコピーコンストラクタの内部でちゃんとSampleBodyのインスタンスを生成し、次のSampleHandleインスタンスに渡してあげる必要がある。じゃないと、代入するSampleHandleインスタンスと代入されるSampleHandleインスタンスは同じSampleBodyインスタンスを持つことになる)

しかしたとえば、インスタンス生成時や破棄の際の問題は、スマートポインタを活用することで解決できる可能性があります。ただし、(C++0xやBoostでのみ実装されていますが)shared_ptrを使う場合はオブジェクトの所有権が共有されるので、注意は必要です。

PImplイディオムはコンパイル時の利点や実装からインターフェースを分離することができる利点があります。

もちろん、参照の際のコストがかかることや継承の問題(多分、SampleHandleを継承しても実装そのもの(SampleBody)は継承できないということ)などの問題はありますが、メリットを生かすことができれば開発時の大きなメリットとなります。


意外と内容そのものは知らないわけでもなかったけれど、こういうのは名前が付いていることを知ることで意識するようになるので今回は勉強になりました。

例で書いたような匿名namespaceとfriendキーワードを用いる方法以外にも実装方法としてはSampleHandleのprivateメンバとしてSampleBodyを実装し、例と同じようにprivateメンバのポインタを使って参照するといった方法があります。
このあたりは検索すればいろいろ出てくるようですし、More C++ Idiomsなどを参考にすると他のイディオムとの関連もわかるので良いかもしれません。