最近読んだ『C++のためのAPIデザイン』という本に興味深い技法が紹介されていてちょっと感動したので備忘録。
きっかけ
C++で、あるクラスのヘッダを以下のように書いたとしましょう。
MyClass.h
#ifndef MyClass_h #define MyClass_h class MyClass { public: MyClass(); virtual ~MyClass(); private: // API利用者に使わせたくない関数 void privateFunc(); }; #endif
このとき、MyClassの利用者には「privateFunc()
」というメンバ関数の存在が丸見えになってしまっています。
利用者は人様が作ったクラスのアクセス指定子を無効化する手段を持っているため、たとえば以下のように書くと、なんとMyClassの外側から合法的にprivate
メンバにアクセスできてしまいます。こわいですね。
#define private public #include "MyClass.h" #undef private int main(int argc, const char * argv[]) { MyClass myClass; myClass.privateFunc(); return 0; }
では、どうやってこの問題を解決すればよいのでしょうか。
Pimplイディオムの導入
基本的な方針は、MyClass.hの中身のうちprivate
なメンバを丸ごとImplに委譲してしまう事です。
MyClass.h
#ifndef MyClass_h #define MyClass_h class MyClass { public: MyClass(); virtual ~MyClass(); private: // コピーコンストラクタと代入演算子を封印する MyClass(const MyClass &); const MyClass &operator =(const MyClass &); // 元のprivateな処理はImplに委譲する class Impl; Impl *mImpl; }; #endif
このヘッダにおいて、Implは前方宣言されているに過ぎないため、外からはmImpl
に関する一切の詳細が隠蔽されています。ここがポイント。
続いて、実装のコードは以下のようになります。
MyClass.cpp
#include <iostream> #include "MyClass.h" class MyClass::Impl { public: // API利用者に使わせたくない関数 void privateFunc() const { std::cout << "こんにちは世界" << std::endl; } }; MyClass::MyClass() { mImpl = new Impl(); } MyClass::~MyClass() { delete mImpl; }
このようにする事で、API利用者からはprivateFunc()
の存在が見えなくなります。
先ほども書きましたが、利用者にとってmImpl
というメンバ変数は見えていても、その先は隠蔽されていますので、以下のようなコードはコンパイルエラーになります。
#define private public #include "MyClass.h" #undef private int main(int argc, const char * argv[]) { MyClass myClass; myClass.mImpl->privateFunc(); // これはできない return 0; }
ちなみに、MyClassからはmImpl
メンバを経由してprivateFunc()
を実行する事が可能です。
試しにMyClassのコンストラクタを以下のように書き換えると、期待した動作になる事が確認できるはずです。
MyClass::MyClass() {
mImpl = new Impl();
mImpl->privateFunc();
}
めでたしめでたし。