PImpl イディオム(pointer to implementation idiom)はC++の有名なイディオムのひとつです。ファイルの依存関係を減らしたい場合(つまりヘッダファイルの中で他のヘッダファイルをあまりインクルードしたくない場合)、実装の詳細を完全に隠したい場合などのときに使います。ポイントはヘッダファイルではインターフェースの宣言のみ行い、実装に関する情報はプライベートメンバ変数ですら隠してしまうところにあります。PImpl イディオムではクラスはImplという(名前はなんでもよいのですが)隠蔽されたネストクラスへのポインタのみをメンバ変数に持ちます。
簡単なサンプルで見てみましょう。
widget.hpp:
class Widget
{
public:
Widget();
~Widget();
Widget(const Widget&);
Widget& operator = (const Widget&);
void swap(Widget&);
void doSomething();
private:
class Impl;
← privateスコープでのプロトタイプ宣言のみ!
std::auto_ptr<Impl> pimpl;
};
ヘッダファイルでのクラス宣言は上のようになります。プライベートスコープでImplというクラスのプロトタイプ宣言がされていますが、Implクラスの定義はソースコード(.cpp)の方に書きます。Widget クラスはただImplへのポインタのみを保持しています。一方、CPPファイルの方はこんな感じになるでしょうか。
widget.cpp:
#include "widget.hpp"
#include <algorithm>
class Widget::Impl
{
public:
...
void doSomething()
{
...
← ここで何かする
}
...
};
Widget::Widget()
: pimpl(new Impl())
{}
Widget::~Widget()
{}
Widget::Widget(const Widget& other)
: pimpl(new Impl(*other.pimpl))
{}
Widget& Widget::operator = (const Widget& other)
{
Widget copy(other);
← ※1
copy.swap(*this);
return *this;
}
void Widget::swap(Widget& other)
{
using std::swap;
swap(pimpl, other.pimpl);
← ※2
}
void Widget::doSomething()
{
pimpl->doSomething();
← Impl クラスに転送するのみ
}
デフォルトコンストラクタではImplをnewしてポインタにセットしています。コピーコンストラクタではImplのコピーをnewしてポインタにセットしています。doSomethingメソッドは単にpimplに転送するだけです。ネストされたImplクラスの実装は外部からは見えませんから、内部の処理やデータ形式をどんなふうに変更してもWidgetクラスのクライアントコードを再コンパイルする必要はありません(再リンクするだけで済みます)。
ちなみに、上のサンプルはコピーコンストラクタとコピー代入演算子をサポートしていますが、コピーを禁止したい場合もあるでしょう。そんな場合はWidgetをNonCopyableにすればいいでしょう。
class Widget : NonCopyable
← ※3
{
...
};
(※1) Copy&Swap 技法 を使っています
(※2) auto_ptr の swap については こちら を参照
(※2) NonCopyable については こちら を参照
追記(2008/8/5):
こちらの記事も参考に…
http://d.hatena.ne.jp/Cryolite/20060108#p1
More C++ Idioms/Checked Delete
関連記事:
- Effective C++ 原著第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)/スコット・メイヤーズ
- ¥3,990
- Amazon.co.jp
- C++ Coding Standards―101のルール、ガイドライン、ベストプラクティス (C++ in‐depth series)/浜田 光之
- ¥2,940
- Amazon.co.jp