はじめに
- Pimplとは
pointer to implementation
の略 - C++でよく用いられるテクニック
- Pimplイディオムを使用すると以下を達成することができる
- APIのインターフェースと実装の分離
- コンパイル依存性の低減
- 本記事ではPimplイディオムについてまとめてみました
Pimplイディオムの使い方
Pimplイディオム使用前
- C++では通常、クラスのプライベートメンバをヘッダファイルに記述します
- 以下がその一例
Example.h
class CExample
{
public:
CExample();
void func1();
void func2();
private:
void func3();
void func4();
private:
std::string data1;
int data2;
}
- 上記のようにプライベートメンバをヘッダファイルにて宣言していると以下のような問題があります
-
情報の隠蔽ができていない
- プライベートメンバが外部から見えてしまっています
- APIの実装を変更した際にクライアントコードにも影響を及ぼす
-
コンパイル時のファイル間依存関係の増加
- 実装の一部を変更、例えば「func5()というプライベートメンバ関数を追加」すると、このヘッダファイルExample.hをインクルードしているすべてのファイルを再コンパイルする必要があります
-
情報の隠蔽ができていない
Pimplイディオム使用後
- そこで、以下のようにPimplイディオムを使用します
- ヘッダファイルにはパブリックメンバのみ開示し、実装ファイル(.cpp)にプライベートメンバを記述します(Implクラスのメンバ関数の実装部分のコードは省略してます)
Example.h
class CExample
{
public:
CExample();
void func1();
void func2();
private:
class Impl;
std::shared_ptr<Impl> pimpl;
}
Example.cpp
class CExample::Impl
{
public:
Impl();
void func3();
void func4();
std::string data1;
int data2;
}
CExample::CExample():pimpl(std::make_shared<Impl>()){}
-
ここで、ヘッダファイルExample.h内にて、定義されていないクラスImplのオブジェクトをなぜ宣言できるのか?オブジェクトのサイズがこの時点では分からないのではないのか?
- ポインタなので、サイズは4 or 8バイトで確定しているのでこの時点ではオブジェクトのサイズは分からなくてもよいので、前方宣言だけで十分
-
Pimplイディオムを使用したクラスをしばしば「ハンドルクラス」と呼びます
-
ハンドルクラスを使ったAPIのインターフェースの定義をどう書くのかを以下で述べます。
ハンドルクラスの使用
-
クライアントコードに影響を及ぼすため、APIのインターフェース部分は変更したくない
- 上記の例でいうと
func1
とfunc2
は変更したくない
- 上記の例でいうと
-
Implクラス内でそれぞれのインターフェースに対応する実装用の関数を用意して、その関数に実装を委譲する
-
以下のコードが実装例です(Implクラスのメンバ関数の実装部分のコードは省略してます)
Example.cpp
class CExample::Impl
{
public:
Impl();
//下記2つが、インターフェースに対応する実装用の関数
void func1();
void func2();
void func3();
void func4();
std::string data1;
int data2;
}
CExample::CExample():pimpl(std::make_shared<Impl>()){}
//実装に変更があっても、以下の2つの関数は変更されません
void CExample::func1()
{
pimpl->func1();
}
void CExample::func2()
{
pimpl->func2();
}
Pimplイディオムのメリット
-
APIのインターフェースと実装の分離
- APIは情報の隠蔽をして、いかにクライアントコードへ影響を及ぼさないかが重要となる
- Pimplイディオムを使用すれば、実装の変更をしてもクライアントコードへ影響が無くなる
-
コンパイル依存性の低減
- Pimplイディオムを使用すると、ヘッダファイルに記述されるのはAPIのインターフェース部分のみとなります
- 実装を変更してもクライアントコードを再コンパイルする必要は無いです
Pimplイディオムのデメリット
- 間接参照のオーバーヘッドが増える
- 全メンバにアクセスする際には、ポインタの間接性一つ分回り道をすることになるので、パフォーマンスが低下する可能性があります