Trip Report: February/March 2008 ISO C++ Standards Meeting
Herb Sutterの"Trip Report: February/March 2008 ISO C++ Standards Meeting"の抄訳。
ISO C++委員会が2月24日から3月1日にかけて米国ワシントン州ベルビューで開催された。
ラムダ関数とクロージャ(N2550)
私にとって最大のニュースはラムダ関数とクロージャの導入を採択したことだ。これはSTLアルゴリズムを格段に使いやすくするだろうし、並列プログラミングのコーディングでも大いに重宝するだろう。並列プログラミングでは、プログラムがフィットすると判断する場所(例えばワーカースレッド)で呼び出される小さなコード断片を、オブジェクトのように簡便に受け渡せるようになるということが重要だからだ。
これまでもC++では関数オブジェクトでこれをサポートしてきた。ラムダやクロージャはそのただのシンタックスシュガーに過ぎない。しかし、「ただ便利なだけ」にもかかわらず、多くの理由から信じられないほどパワフルな便利さをもたらしてくれる。例えば、あるコードを、どこか遠く離れた場所にではなく、利用するまさにその場所に書けるようになる。
例:コンソールにコレクションを書き出す
例えばウィジェットのコレクションから各要素を書き出したいとする。
// 現状のC++で、コレクションをcoutに書き出す:その1 for( vector<Widget>::iterator i = w.begin(); i != w.end(); ++i ) cout << *i << " ";
あるいは、C++には既にこの目的のための特別に用意されたostream_iteratorがあるので、これを利用してもよい:
// 現状のC++で、コレクションをcoutに書き出す:その2 copy( w.begin(), w.end(), ostream_iterator<const Widget>( cout, " " ) );
C++0xでは、ラムダを使って適切な関数オブジェクトをその場で書き下せる:
// C++0xで、コレクションをcoutに書き出す for_each( w.begin(), w.end(), []( const Widget& w ) { cout << w << " "; } );
(使いやすさに関する追記:これらの例をチェックするためにコンパイラで試したが、私が一回で正しく書けたのはただひとつ、このラムダ版だけだ。いや分かってる。<からかい type="図々しい">そう、つまり私はあるコンパイラ上で試したということだ。いや、VC++ Version 10の製品版機能の宣伝をしているわけじゃない。少なくとも今は。からかい>)
例:Weight() > 100を満たす要素を見つける
別の例として、ウィジェットのコレクションから重さが100より重い要素を見つけたいとする。今ならこう書くだろう:
// 現状のC++で、ファンクタを用いて find_if を呼び出す // 関数の外、名前空間スコープで class GreaterThan { int weight; public: GreaterThan( int weight_ ) : weight(weight_) { } bool operator()( const Widget& w ) { return w.Weight() > weight; } }; // 実際に使う場所で find_if( w.begin(), w.end(), GreaterThan(100) );
ここで、(a)bind2ndのようなバインダヘルパーならC++98標準にも入っている、とか、(b)Boostのbindやlamdaライブラリがある、とか指摘する人もいるだろうが、少なくとも可読性があって保守性の高いコードに興味がある場合には、それらは本当の助けにはならない。もし疑うなら、試してみて欲しい。
C++0xではこう書くだけでよい:
// C++0xで、ファンクタを用いて find_if を呼び出す find_if( w.begin(), w.end(), []( const Widget& w ) -> bool { w.Weight() > 100; } );
うーん、ずっと良いね。
ほとんどのSTLアルゴリズムはループだ…ふむ…
実のところ、ループに似たアルゴリズムはいまやどれもループとして使える。std::for_eachとstd::transformを使った簡単な例を挙げると:
for_each( v.begin(), v.end(), []( Widget& w ) { ... ... w を利用したり更新したり ... ... } ); transform( v.begin(), v.end(), output.begin(), []( const Widget& w ) -> AnotherType { ... return SomeResultCalculatedFrom( w ); } );
ふむ。ひょっとすると…今後コンパイラがC++0xのラムダをサポートし始めると、ループ本体の終わりの"});"を見かけることが増え始めるのかも。
並列プログラミングでも待ち遠しい
最後に、スレッドプールで走るコード断片を渡したい場合を見てみよう。外の名前空間スコープでファンクタクラスを定義しなければならなかったあのウンザリはなくなり、直接こう書けばよい:
// C++0xでスレッドプールでの仕事を渡す mypool.run( [] { cout << "Hello there (from the pool)"; } );
素晴らしい。
認可されたその他の機能
- N2535 名前空間の関連付け(インライン名前空間)
- N2540 コンストラクタの継承
- N2541 新しい関数宣言シンタックス
- N2543 STL単方向リンクリスト(forward_list)
- N2544 制限されないunion
- N2546 記憶クラス指定子としてのautoの除去
- N2551 可変長テンプレート版のstd::min, std::max, std::minmax
- N2554 スコープつきアロケータモデル
- N2525 アロケータに固有のswapおよびmoveの振る舞い
- N2547 シグナルハンドラにおけるロックフリーなatomic
の許可 - N2555 可変長テンプレートテンプレートパラメータの拡張
- N2559 例外のネスト(またはラップされた例外)