Boost.Foreachは簡単に使えて、かつ、メリットも大きいライブラリだ。
forループを Perlなんかの foreachのように記述できるようになる。
たとえば配列だったら
double a[] = {1,2,3,4};
for (int i = 0; i < sizeof(a)/sizeof(*a); ++i) {
a[i] *= -1;
}
なんて書いていたのを
double a[] = {1,2,3,4};
BOOST_FOREACH(double& d, a) {
d *= -1;
}
てな感じにループカウンタなどを気にしないで書ける。
これがものすごーく快適。
ループの終了条件があっているかだとか、多重ループでは正しいカウンタをインクリメントしているのかだとかに労力を費やす必要がなくなるのは本当に大きい。
listなんかにも当然使えるのだけど、
for (std::list<int>::const_iterator it = l.begin(); it != l.end(); ++it) {
std::cout << *it << "\n";
}
が
BOOST_FOREACH(int n, l) {
std::cout << n << "\n";
}
と書けて、プログラマの意図するところも短く明確に伝わるようになる。
シーケンスとして扱えるものは配列やlistだけでなく、以下にあげたように
rangeになっているものならOK。さらに自分の作ったコンテナ型でもちゃんとコンセプトを満たせば利用できるようになる。
- STLコンテナ(とその仲間たち)
- 配列
- NULL終端文字列 (char[], wchar_t[], char*, wchar_t*)
- std::pair<Iterator, Iterator>
パフォーマンス面では、ドキュメントをみるかぎり
- 動的メモリ確保や、仮想関数呼び出し、関数ポインタ呼び出しなどが一切なく、コンパイラの最適化を阻害しない。
- 普通にループをハードコードした場合にくらべて数%程度のパフォーマンス犠牲しかない。
とのことなので問題になることはほとんどなさそう。
注意するべき点としては2つほどドキュメントで挙げられている。
1つはBOOST_FOREACHはマクロを使って実装されているので、カンマの数は1つでなくてはならない。
とすると、mapなんかの場合に問題になる。
std::map<char*, int> m;
BOOST_FOREACH(std::pair<char*, int> p, m) { // コンパイルエラー! カンマが複数個ある。
..
}
これはtypedefによって回避するか
std::map<char*, int> m;
typedef std::pair<char*, int> P;
BOOST_FOREACH(P p, m) { // OK!
..
}
あらかじめ内容を受け取る変数を宣言しておけばいい。
std::map<char*, int> m;
std::pair<char*, int> p;
BOOST_FOREACH(p, m) { // OK!
..
}
もう1つはループ中に、ループの終了条件を表すイテレータ(end()で返すもの)を無効にするような操作を行ってはならないということ。
std::vector<int> v(4);
BOOST_FOREACH(int n, v) {
v.push_back(1); // vectorだとpush_backによって 以前にend()によって返されたイテレータが無効になるのでアウト。
}
これはfor文を素直に使うしかない。
ところでBOOST_FOREACHはマクロを使って実装されているのだが、マクロの有名な副作用、つまり引数が複数回評価されて大変な事態を引き起こすことがないように変態テクニックを駆使して実装されている。
以下のようなコードでも GetStringFromStream()関数が呼び出されるのは1回限りであることが保証されている。
BOOST_FOREACH(char c, GetStringFromStream()) {
...
}
どのようなトリックを使って実現しているかは
このページに記述されているが、とっても面白いので興味のある人は一読をオススメする。
記事のタイトルが "Conditional Love"(訳: 条件演算子、愛してるよ、条件演算子)になっているのが最初意味不明だったが、読み進めていくうちに謎が解き明かされていく。
C/C++の世界では悪役にされがちな条件演算子(?演算子)が、BOOST_FOREACHではとてつもなく重要な働きをしていることに感動すら覚えマスタ。