c++でpimplイディオムの書き方
はじめに
私は基本的にヘッダオンリーでC++のソースを書くタイプなのですが、研究で使うRCカーの制御プログラムが肥大化してきて、フルビルドに10分近くかかるようになってしまいました。そのまま実装とヘッダに分けても良いのですが、どうせならと思ってPimplイディオムを導入しました。
以下の記事を参考にさせて頂きました。 qiita.com
メリット・デメリット
Pimpl とは “Pointer to Implementation"から来ているみたいですね。
メリット
- private なものを隠すことができる
- コンパイル時間が短くなる
デメリット
- ソースが肥大化する
- 無駄にメモリ確保が必要
アンチパターンなんて記事も見るので、良い悪いは人それぞれだとは思いますが、個人的にprivateなものがヘッダからは見えず、ソースが読みやすくなることと、privateな部分での変更をいちいちヘッダにも反映させなくて良いのが気に入ってます。
使い方
ヘッダオンリー例
手順どおりにやれば至ってシンプルです。
まず、以下のようなソースを例に説明します。
test.h
#ifndef TEST_H_ #define TEST_H_ #include <iostream> class Test { private: int pri_value; void twice() { pri_value *= 2; } void print() { std::cout << "pri_value: " << pri_value << std::endl; } public: int pub_value; Test() : pri_value(2), pub_value(4) { std::cout << "constructor" << std::endl; } ~Test() { std::cout << "destructor" << std::endl; } void process() { twice(); print(); } int process2(const int v_) { return pub_value * v_; } }; #endif // TEST_H_
main.cpp
#include "test.h" int main(void){ Test test; test.process(); std::cout << "result of process2: " << test.process2(2) << std::endl; test.pub_value = 100; std::cout << "result of process2: " << test.process2(4) << std::endl; }
実行例
$ ./a.out constructor pri_value: 4 result of process2: 8 result of process2: 400 destructor
手順
test.h
のprivateにTestImpl(クラス名+Impl)の前方宣言と、そのクラスへのポインター(pimpl)を設置test.cpp
を作成し、TestImplクラスを宣言test.h
のprivate, publicの中身(変数、関数、コンストラクタ…)を新しいクラスに移動- Testクラスのpublic変数を参照に変更
test.cpp
の、TestクラスコンストラクタでTestImplをnewして、参照で定義したpublic変数をpimplの保持する実体へ繋ぐtest.cpp
にTestクラスのpublic関数の実装をpimplの関数の実体へと繋ぐ
書き換え後
test.h
#ifndef TEST_H_ #define TEST_H_ #include <iostream> #include <memory> #include <vector> class Test { private: class TestImpl; std::unique_ptr<TestImpl> pimpl; public: int& pub_value; Test(); ~Test(); void process(); int process2(const int); }; #endif // TEST_H_
test.cpp
#include "test.h" class Test::TestImpl { private: int pri_value; void twice() { pri_value *= 2; } void print() { std::cout << "pri_value: " << pri_value << std::endl; } public: int pub_value; TestImpl() : pri_value(2), pub_value(4) { std::cout << "constructor" << std::endl; } ~TestImpl() { std::cout << "destructor" << std::endl; } void process() { twice(); print(); } int process2(const int v_) { return pub_value * v_; } }; Test::Test() : pimpl(new TestImpl()), pub_value(pimpl->pub_value) {} Test::~Test() {} void Test::process() { pimpl->process(); } int Test::process2(const int v_) { return pimpl->process2(v_); }
main.cpp
は同じ
実行例
$ ./a.out constructor pri_value: 4 result of process2: 8 result of process2: 400 destructor
コメント
- unique_ptrにすることでポインタ管理の心配いらず
- 関数を一度無駄に経由することになるので、コピーコストラクタ走るのが嫌な時は転送すれば良い(コピーよりはまし)
まとめ
確かにソースは肥大化しますが、こっちのほうが1つのファイルで何やってるかわかるので、読みやすいような気がします。