pimplイディオム
Problem
ファイル間の依存関係が増加すると、コンパイルに要する時間も増加していきます。ファイル間の依存関係を減少させるためには、どのようにすれば良いでしょうか?
Solution
例えば、以下のようなコードがあったとします。
Object.h
しかし、Objectクラスにメンバ変数を追加した場合はどうでしょうか?
Object.h
Object.h
これがpimplイディオムと呼ばれるイディオムです。
pimplイディオムは別の名前(pointer to implementation idiom, private implementation idiom, handle/body idiom)で呼ばれることもあります。また、Bridgeパターンの一種であるとされたりもします。
ファイル間の依存関係が増加すると、コンパイルに要する時間も増加していきます。ファイル間の依存関係を減少させるためには、どのようにすれば良いでしょうか?
Solution
例えば、以下のようなコードがあったとします。
Object.h
#ifndef INCLUDE_GUARD_OBJECT_H
#define INCLUDE_GUARD_OBJECT_H
#include <string>
class Object {
public:
explicit Object(const char* name);
~Object();
void print();
private:
std::string name_;
};
#endif // INCLUDE_GUARD_OBJECT_H
Object.cpp
#include <iostream>
#include "Object.h"
Object::Object(const char* name) : name_(name)
{
}
Object::~Object()
{
}
void Object::print()
{
std::cout << name_ << std::endl;
}
main.cpp
#include "Object.h"
int main()
{
Object object("instance");
object.print();
return 0;
}
このコードに対してObject::print()の動作(例えば出力フォーマット)を変更した場合、コンパイルが必要なのは実装ファイル(Object.cpp)だけで、クライアントコード(main.cpp)の再コンパイルは必要ありません。必要なのは再リンクだけです。しかし、Objectクラスにメンバ変数を追加した場合はどうでしょうか?
Object.h
#ifndef INCLUDE_GUARD_OBJECT_H
#define INCLUDE_GUARD_OBJECT_H
#include <string>
class Object {
public:
explicit Object(const char* name);
~Object();
void print();
private:
std::string name_;
};
#endif // INCLUDE_GUARD_OBJECT_H
Object.cpp
#include <iostream>
#include "Object.h"
Object::Object(const char* name) : name_(name)
{
}
Object::~Object()
{
}
void Object::print()
{
std::cout << "[" << count_++ << "] " << name_ < std::endl;
}
Objectクラスのメンバ変数が変更された場合、それが例えprivateメンバであったとしても、クライアントコードの再コンパイルが必要となります。この変更は本質的にはクライアントコードとは無関係であるので、このような依存性は避けたいところです。では、どのようにすれば、このような依存関係を断ち切ることができるでしょうか?。それは、Objectのメンバ変数をポインタで隠蔽することです。Object.h
#ifndef INCLUDE_GUARD_OBJECT_H
#define INCLUDE_GUARD_OBJECT_H
class Object {
public:
explicit Object(const char* name);
~Object();
void print();
private:
struct ObjectImpl;
ObjectImpl* pimpl_;
};
#endif // INCLUDE_GUARD_OBJECT_H
Object.cpp
#include <iostream>
#include <string>
#include "Object.h"
struct Object::ObjectImpl {
explicit ObjectImpl(const char* name) : name_(name), count_(0) {}
std::string name_;
int count_;
};
Object::Object(const char* name) : pimpl_(new ObjectImpl(name))
{
}
Object::~Object()
{
delete pimpl_;
}
void Object::print()
{
std::cout << "[" << pimpl_->count_++ << "] " << pimpl_->name_ < std::endl;
}
これで、Object::ObjectImpl構造体のメンバ変数が変更されても、クライアントコードの再コンパイルは必要無くなりました。これがpimplイディオムと呼ばれるイディオムです。
pimplイディオムは別の名前(pointer to implementation idiom, private implementation idiom, handle/body idiom)で呼ばれることもあります。また、Bridgeパターンの一種であるとされたりもします。
プログラミング言語C++
![]() | プログラミング言語C++第3版 Bjarne Stroustrup (1998/12) アジソンウェスレイパブリッシャーズジャパン この商品の詳細を見る 正誤表はこちら |
C++言語の開発者であるBjarne Stroustrup氏によって書かれたC++のバイブルで、C言語のK&Rに相当するものと考えてもらえば間違いない。
本書は、C++の言語仕様や設計思想に関する書籍であって、コーディングスタイルに関する書籍ではない。また、そのページ数からしても決して初心者向きではない。つまり、プロフェッショナル向けの書籍であり、プロフェッショナルなC++プログラマであれば、一度は目を通す必要がある一冊です。
通称TC++PL (The C++ Programming Language)。
RAIIイディオム - Part2
Problem
RAIIイディオムに掲載したFileResourceクラスにはいくつか問題点があります。その問題点とは何でしょうか?
Solution
まず、FileResourceクラスを見てみましょう。
この問題の解決策としては、リファレンスカウントを用いてリソースを共有する方法もありますが、RAIIイディオムの一般的な使用目的からするとインスタンスのコピーを禁止することが望ましいことです。
インスタンスのコピーを禁止するには、コピーコンストラクタとコピー代入演算子をprivateに宣言します(宣言だけで定義は必要ありません)。
さて、FileResourceクラスの問題点はまだあります。それは動的割り当てが行えるということです。
この問題の解決策としては、RAIIのインスタンスの動的割り当てを禁止して、RAIIのインスタンスはスタックに置かれる自動変数だけに限定することです。
クラスの動的割り当てを禁止するには、new演算子をprivateに宣言します(宣言だけで定義は必要ありません)。
さて、この「コピーを禁止するイディオム」と「動的割り当てを禁止するイディオム」は、それぞれ単独でも非常に有益なイディオムなので覚えておくと良いでしょう。
関連記事
・RAIIイディオム
・RAIIイディオム - Part2
RAIIイディオムに掲載したFileResourceクラスにはいくつか問題点があります。その問題点とは何でしょうか?
Solution
まず、FileResourceクラスを見てみましょう。
class FileResource {
public:
explicit FileResource(std::FILE* fp) : fp_(fp) {}
~FileResource() { if (fp_) std::fclose(fp_); }
std::FILE* get() { return fp_; }
private:
std::FILE* fp_;
};
このクラスの問題点のひとつはインスタンスのコピーが行えるということです。
void func(const char* file)
{
FileResource fileResource(std::fopen(file, "r"));
FileResource fileResourceCopy(fileResource);
// 何らかの処理を行う
}
このようにコピーインスタンスの作成が可能であり、fileResourceのデストラクタとfileResourceCopyのデストラクタでstd::fclose()が2回呼び出されることになります。この問題の解決策としては、リファレンスカウントを用いてリソースを共有する方法もありますが、RAIIイディオムの一般的な使用目的からするとインスタンスのコピーを禁止することが望ましいことです。
インスタンスのコピーを禁止するには、コピーコンストラクタとコピー代入演算子をprivateに宣言します(宣言だけで定義は必要ありません)。
class FileResource {
public:
explicit FileResource(std::FILE* fp) : fp_(fp) {}
~FileResource() { if (fp_) std::fclose(fp_); }
std::FILE* get() { return fp_; }
private:
FileResource(const FileResource&);
FileResource& operator=(const FileResource&);
std::FILE* fp_;
};
これでFileResourceインスタンスのコピーを行おうとすると、コンパイルエラーが発生します。さて、FileResourceクラスの問題点はまだあります。それは動的割り当てが行えるということです。
void func(const char* file)
{
FileResource* fileResource = new FileResource(std::fopen(file, "r"));
// 何らかの処理を行う
delete fileResource;
}
このようにnew演算子でFileResourceインスタンスを動的に割り当てることが可能です。RAIIのインスタンスを動的に割り当ててしまうと、RAIIイディオムの本来の目的(利点)が失われてしまいます。この問題の解決策としては、RAIIのインスタンスの動的割り当てを禁止して、RAIIのインスタンスはスタックに置かれる自動変数だけに限定することです。
クラスの動的割り当てを禁止するには、new演算子をprivateに宣言します(宣言だけで定義は必要ありません)。
class FileResource {
public:
explicit FileResource(std::FILE* fp) : fp_(fp) {}
~FileResource() { if (fp_) std::fclose(fp_); }
std::FILE* get() { return fp_; }
private:
FileResource(const FileResource&);
FileResource& operator=(const FileResource&);
void* operator new(std::size_t);
void* operator new[](std::size_t);
std::FILE* fp_;
};
これでFileResourceクラスを動的に割り当てようとすると、コンパイルエラーが発生します。さて、この「コピーを禁止するイディオム」と「動的割り当てを禁止するイディオム」は、それぞれ単独でも非常に有益なイディオムなので覚えておくと良いでしょう。
関連記事
・RAIIイディオム
・RAIIイディオム - Part2
reverse_iteratorを使おう
Problem
std::find_ifアルゴリズムを使用すれば条件を満たす最初のイテレータを取得することができますが、条件を満たす最後のイテレータを取得したい場合はどのようにすれば良いでしょうか?
なぜ、std::find_last_ifアルゴリズムが無いのでしょうか?
Solution
reverse_iteratorを使いましょう。アルゴリズム(標準アルゴリズムだけでなく、自作したアルゴリズムであっても)にはiteratorだけではなく、reverse_iteratorを渡すことができます。
しかし、アルゴリズムではなくコンテナのメンバ関数を使用する際には注意が必要です。なぜなら、コンテナのメンバ関数は引数にiteratorを要求するからです。
具体的にはstd::vectorの場合、insert()、erase()、assign()およびコンストラクタなどです。
以下は、std::vectorの最後の奇数値を検索して、各種メンバ関数を使用する例です。
ソースコード
Notes
1. Effective STL - 第28項 reverse_iteratorの基底iteratorの使い方を理解しよう
std::find_ifアルゴリズムを使用すれば条件を満たす最初のイテレータを取得することができますが、条件を満たす最後のイテレータを取得したい場合はどのようにすれば良いでしょうか?
なぜ、std::find_last_ifアルゴリズムが無いのでしょうか?
Solution
reverse_iteratorを使いましょう。アルゴリズム(標準アルゴリズムだけでなく、自作したアルゴリズムであっても)にはiteratorだけではなく、reverse_iteratorを渡すことができます。
しかし、アルゴリズムではなくコンテナのメンバ関数を使用する際には注意が必要です。なぜなら、コンテナのメンバ関数は引数にiteratorを要求するからです。
具体的にはstd::vectorの場合、insert()、erase()、assign()およびコンストラクタなどです。
以下は、std::vector
ソースコード
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
template<class InputIterator>
inline void print_all(InputIterator first, InputIterator last)
{
std::cout << " " << std::ends;
while (first != last) {
std::cout << *first << ", ";
++first;
}
std::cout << std::endl;
}
struct odd : public std::unary_function<int, bool> {
bool operator()(int x) const { return x % 2; }
};
int main()
{
typedef std::vector<int> container;
typedef container::reverse_iterator reverse_iterator;
container v;
std::cout << "push_back()" << std::endl;
for (int i = 0; i < 5; ++i) {
v.push_back(i);
}
print_all(v.begin(), v.end());
std::cout << "insert()" << std::endl;
container::reverse_iterator rit0 = std::find_if(v.rbegin(), v.rend(), odd());
std::cout << " last odd = " << *rit0 << std::endl;
v.insert(rit0.base(), 9);
print_all(v.begin(), v.end());
std::cout << "erase()" << std::endl;
container::reverse_iterator rit1 = std::find_if(v.rbegin(), v.rend(), odd());
std::cout << " last odd = " << *rit1 << std::endl;
v.erase((++rit1).base());
print_all(v.begin(), v.end());
std::cout << "assign()" << std::endl;
container::reverse_iterator rit2 = std::find_if(v.rbegin(), v.rend(), odd());
std::cout << " last odd = " << *rit2 << std::endl;
container vv;
vv.assign(v.begin(), (++rit2).base());
print_all(vv.begin(), vv.end());
return 0;
}
実行結果push_back() 0, 1, 2, 3, 4, insert() last odd = 3 0, 1, 2, 3, 9, 4, erase() last odd = 9 0, 1, 2, 3, 4, assign() last odd = 3 0, 1, 2,
Notes
1. Effective STL - 第28項 reverse_iteratorの基底iteratorの使い方を理解しよう
![]() | Effective STL―STLを効果的に使いこなす50の鉄則 スコット メイヤーズ (2002/01) ピアソンエデュケーション この商品の詳細を見る |


