オブジェクト指向の重要な概念のひとつに「カプセル化」というものがあります。
隠蔽化とも呼ばれ、ある機能を使いたい時に、ユーザーが実装詳細を意識しなくても動作し、ある日、実装の内容がドラスティックに変更されても、ユーザーは特に何もしなくても昨日までと同じようにその機能を使い続けられるということを期待するものです。
適切なカプセル化の思想の元に設計されたコードは変更に対して柔軟です。
あなたが学生時代に書いた、擬似乱数を生成するオブジェクトMyRandは昨日まで線形合同法で実装されていました。しかし、今日、急に思い立って擬似乱数の生成方法をxorshiftに変更したとしても、MyRandオブジェクトを使う側はその変更によって呼び出しコードを修正する必要はありません。
※この話はカルドセプトサーガおよび株式会社ロケットスタジオとは一切関係ありません。(http://www26.atwiki.jp/gcmatome/pages/2574.html)
クラスの実装を呼び出し側から完全に分離し、隠蔽する手法にPimplイディオムというものがあります。
通常、クラス設計では公開するヘッダーファイルの定義の中にpublicからprivateまでの定義をひととおり書きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class PasswordChecker{ public: // 公開 PasswordChecker(); ~PasswordChecker(); public: bool check(const char* const pass); private: // 非公開 int makeCRC(); private: char pass_[16]; int crc_; }; |
このクラスにメンバを足したり減らしたり、またはコメントを追加したり、何かしら変更を加えると、このクラスを使う(=ヘッダーファイルをインクルードしている)箇所全てが再コンパイルの対象になります。ライブラリの深いところのクラスをちょっと書き換えただけなのにフルビルドコースという話は決して珍しくありません。
また、ユーザーはクラスの公開されている部分にしか基本的にはアクセスできませんが、非公開関数のインターフェースとメンバ変数の詳細は見えています。
ああ、パスワードは最大16文字なんだな、とかCRC使ってるんだなとかの情報が読み取れてしまいますね。本例は多少作為的ですが、それでも外部に提供するクラスライブラリ等、なるべく情報を出したくないケースもあります。
データサイズとデータのレイアウトが想像できると、通信パケットやメモリダンプから情報を抜きやすくなってしまいますからね。これはもう圧倒的に抜かれます。
「公開されている部分にしか基本的にはアクセスできません」と書きましたが、基本的じゃない場合、その気になればメンバ変数pass_にも一瞬でアクセスできます。
→アクセス指定子の無効化
クラス編集による再ビルドの影響範囲をなるべく抑えたい時やプライベートメンバをユーザーが見れる状況が好ましくない場合、Pimplイディオムを使ってクラスの実装を呼び出し側から完全に分離しましょう。
Pimplイディオム実装方法
ヘッダーファイル側の定義ではprivateメンバを書くのではなく内部実装の為のクラスのポインタのみ持ちます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <memory> class PasswordChecker { public: // 公開 PasswordChecker(); ~PasswordChecker(); public: bool check(const char* const pass); private: // 非公開 class Impl; std::unique_ptr<Impl> impl_; }; |
そして.cpp側でImplの詳細を定義することで実装を完全にヘッダーファイルから分離できます。
1 2 3 4 5 6 7 8 9 10 |
#include "password_checker.hpp" class PasswordChecker::Impl { // // 実装に必要な定義をつらつらと書く }; PasswordChecker::PasswordChecker() : impl_(new Impl) // 実装クラスを生成 {} |
impl_経由で処理を呼び出します。
1 2 3 |
bool PasswordChecker::check(const char* const pass) { return impl_->check(pass); } |
ちなみにPimplはピーインプルと読まれることが多く、Pointer To ImplementationやPrivate Implementationの意味で使われます。
Pimplイディオムは実装の詳細が分離できてコンパイル時間の減少にも貢献できる代償として、処理を呼び出す際にipml_を経由する過程で間接参照が必ず一回入るので、その分のオーバーヘッドのコストが発生する点だけ認識しておいてください。あと実際問題として、クラスの実装が委譲だらけになるので書くのが手間です。
まとめ
- Pimplイディオムを使うとクラスの定義と実装を完全に分離できる
- Pimplイディオムはコンパイル時間の抑制になる
- Pimplイディオムには間接参照のオーバーヘッドが発生する