pimpl, scoped_ptr
c++で、あるクラスAの実装とクラスAの利用者との間の依存関係を断ち切る為に使われるテクニックに、pimplイディオムというのがあるらしい。おぉ!これは便利、と思って、じゃぁ使ってみましょうか、ということでちょっと調べた。
そもそもpimplイディオムとは
A.h
class A { public: A(); ~A(); void doSomething(); private: class Impl; Impl* impl; };
A.cpp
#include "A.h" class A::Impl { public: Impl() { // construct impl } ~Impl() { // destruct impl } void doSomething() { // do something } }; A::A() : impl(new Impl()) {} A::~A() { delete impl; } void A::doSomething() { impl->doSomething(); }
みたいな感じで、実装を内部クラスに丸々委譲してしまうもの。こうすると、実装に関する情報が全部A.cppの中にまとまっていて、Aの実装を変更した時に、その変更の影響がA.hをインクルードしているクラスAの利用コードに及ばない、というもの。
まず、生ポインタを嫌ってboost::shared_ptrを使うときの注意。shared_ptrだと、クラスAのコピーコンストラクタとoperator=をきちんとディープコピーにしてあげないと(暗黙に作成されるものを使っていると)、中身のデータがクラスAの複数のインスタンスで共有されちゃうよ、と。まぁ、これはでもポインタ使って居る以上当然想像されるべき振る舞い。
で、これに起因するうっかりミスを防ぐためにコピーコンストラクタとoperator=をprivateに隠したscoped_ptrというのがある。「scoped_ptrはpimplイディオムの実装によく使われます」と説明している人も居るぐらいなので、まぁpimplにはこのスマポなんだろうと理解していた。ところが気楽に使っていると?なコンパイルエラーが出る。以下、その例。scoped_ptrもスマートポインタなので、ポインタの解放はお任せしたい。で、こんなコードを書く。
B.h
#include <boost/scoped_ptr.hpp> class B { public: B(); void doSomething(); private: class Impl; boost::scoped_ptr<Impl> impl; };
B.cpp
#include "B.h" using namespace boost; class B::Impl { public: Impl() { // construct impl } ~Impl() { // destruct impl } void doSomething() { // do something } }; B::B() : impl(new Impl()) {} void B::doSomething() { impl->doSomething(); }
これは、B.cppのコンパイルは通るけど、別のファイルで
#include "B.h" ... B b; ...
とした時点でコンパイルエラーが発生する。これを正しく使うためには、
B.hで
~B();
とデストラクタの宣言だけをしておいて
B.cppで、クラスB::Impleの定義よりも後に
B::~B(){}
とデストラクタの定義を書かなければならない。何でこんなことになるかっていうと、B.hにデストラクタの宣言が無い時のコンパイラの振舞に理由がある。B.hにデストラクタの宣言が無いと、コンパイラは暗黙的に、何もしないデストラクタがB.hにあるものと見なす。するとすると、この何もしないデストラクタの中では、implの破棄が行われるが、implはscoped_ptr
c++難しすぎるよ。