OSC2011 Hokkaidoで行われたC++0xセミナーでの説明等への補足等々...
旅記事はまたあとで書くかも。書かないかも
final/overrideについて
final/overrideについて、発表中にキーワードではないと訂正が入りましたが、どう言うことかというと
const int const = 0;
はご存知の通りill-formedです。constという変数名をもつ変数を宣言することはできません。これはconstがキーワードだからです。
一方final/overrideは文脈依存キーワード(contextual keyword)というものです。キーワードという単語が入ってますが、C++0x標準仕様のキーワードの項(厳密にはFDISなので標準仕様では無いですが、これ以後極端に大きな変更はないはずです。無いと信じます。やるな)にはfinal/overrideは予約済み識別子として登録されていません。これが意味することは何かというと、
struct final final {} final;
というのが極端な例ですが、これはC++0x的にwell-definedということです。
この文はfinalというオブジェクトはstruct finalというこれ以上派生できない(final)型であるという意味です。
特定の部分でのみ意味が変わるということです。
Functor/Predicateについて
Functor(ふぁんくたー、日本名:関数オブジェクト)での説明スライドで、別名?エイリアス?のリストの中に、述語関数とPredicate(ぷれでぃけーと、述語関数に同じ)が記載されていましたが、これは微妙にというか間違いです。
FunctionとFunctorは見た目同じように動作しますが厳密には違うものです。とはいえ説明は面倒と言えば面倒なのでここでは同じとします(関数が一級オブジェクトになった!といえるのかな?)。
で、本題FunctorとPredicateの違いですが、まぁ簡単です。Functorはこんな感じ
Return functor( Arg1, Arg2, ... Argn )
で、Predicateは
bool predicate( Arg1, Arg2, ... Argn )
一目瞭然ですが、Predicateは単純に戻り値の型がbool(真偽値)になってます。
厳密に言うと任意の個の引数をとり、その関係性についての真偽値を返します。つまり一般的な関数(副作用を含む可能性あり)の成功か失敗かというのはPredicateではありません。
では、どういうものがPredicateに該当するかというと比較演算子などがこれにあたります。第一引数と第二引数を比較し、その関係について真偽値を返しています。
有名なところだとstd::sortはオーバーロード版に3引数を取るものがあります。iterator 2つとpredicateです。通常std::sortは
std::sort( sequence.begin(), sequence.end() );
と使いますが、これは3引数版だと
std::sort( sequence.begin(), sequence.end(), std::less< decltype( sequence )::value_type >() );
と等価です。(decltypeについては説明されてるはずなので省きます)
ここで出てくるstd::lessというのがpredicateです。具体的には第一引数と第二引数を比較し、第一引数が第二引数より小さかったらtrueを返します。別のpredicateにstd::greaterというのが有りあますが、これは逆に第二引数の方が小さかったらtrueを返します。
様々なpredicateを用いることで、std::sortは逆順にソートするようになったり全く別の順番でソートすることができます。
例えば英語を用いて説明すると
He is a soccer player.
という文があったとします。ここには主語Heと目的語soccer playerがあります。そしてこの間に述語(!)isがあり、Heとsoccer playerの関係を示しています。
predicateはこの文を満たすかどうかを問い合わせる質問文と捉えることができます。
上の例文の質問文は
Is he a soccer player?
となります。この文に対する返答(戻り値)は一般的にyes(true)かno(false)になります。関数っぽく書くと
is( he, soccer_player );
となります。このis関数の戻り値もtrue(yes)かfalse(no)になり、上記の質問文とその返答(戻り値)に一致します。
この様に与えられた引数自身または引数同士の関係を表すのがpredicateとなります。項数は関係ありません。
単項であれば、(意味上では)std::is_sortedが挙げられます。std::is_sortedは与えられたsequenceがソート済みであるかどうかを返します。
同様に3項以上にもpredicateはあり、任意の数与えられたすべての引数が等しいかどうかを返す関数ももちろんpredicateです。
といった感じでしょうか。説明は。まぁ、引数の関係性に関する真偽値を返すのがpredicateです。記号論理学とかやってる人からするともしかしたらいろいろ突っ込まれるかもしれないですが、C++においては大体このような解釈で問題ないはずです。
lambdaの実行効率について
C++0xから導入されたlambdaですが、実行効率について危惧されてる方がいらっしゃいました。lambda自体はもともとあったテクニックを構文化した構文糖に過ぎません。
例えば
void hoge() { fuga( []{ std::cout << "lambda!" << std::endl; } ); }
というlambdaがあった場合、これは
void hoge() { struct { void operator()() { std::cout << "alternative!" << std::endl; } } __unnamed_lambda_functor__; fuga( __unnamed_lambda_functor__ ); }
と置き換えることができます。__unnamed_lambda_functor__という名前はあくまで例であって本来ユーザーがどうやってコードを書いても参照できない様な名前が使用されます。(さらに言うとC++03では上のコードはill-formedとなりますが、詳細は割愛します)
ちなみに
void f() { auto f = []{}; auto g = []{}; }
というものがあったとき、fとgは全く同じ定義で同じ動作しますが、fとgの型は全く別の型です。f=gやg=fはill-formedです。
キャプチャがある場合は
int x, y, z; auto f = [&]{ x = y = z = 0; };
このlambdaは
int x, y, z; struct { int &_x, &_y, &_z; void operator()() { _x = _y = _z = 0; } } f = { x, y, z };
とだいたい等価です。
で、本題ですが、効率とかについてです。
参照を使っているのでどう考えてもコピーのにかかるコストなんてものは有り得ません。
更に言うとC++にはRVO(Return value optimization)やNRVO(Named RVO)といった最適化テクニックがあり、現在普及しているコンパイラではほとんどサポートされているのでロスはありません。
キャプチャには参照だけではなくコピーもありますが、こちらは明示的にコピーを指定してるのでコピーのコストは発生します。(というか発生しないと基本的に意味がぁぁぁってなります)
で、何の考え無しにコピーするともちろん重いので基本参照で、何かあるときだけコピーを使うようにすると最低限のコストで実行できます。
また、lambdaは定義上単純な構文糖ですがその場限りの定義であり、そこ以外では基本的に使われないことがわかります。
他の関数と違い、こちらはそこだけで使われるので単純なインライン化以上に極限まで最適化できます。通常の関数は他の場所で使われる可能性があるので最適化には限度があります。(説明むずいな・・・alignmentとかによるreorderとかそういった類のこととか)
と言ってもlambdaを受け取る関数側の作り方によっては通常のものとさほど変わらないこともあります。
とりあえず私が言いたいのは、最適化に関して人間ができることは基本的にないので、構文糖を使ってコンパイラ様が好き勝手できるようにしたほうがいいと思います。なによりいちいち型とか書きたくないですし。
まとまってないけどまとめ
機能は使いましょう。shared_ptrをみんなちやほやしてますが、unique_ptrを忘れないであげてください。でもauto_ptrはそっとしといてあげてください。ここまで頑張ったんですから。
なんか質問とかあったらflast@[ac-room.org|flast.jp]あたりにメールを投げるなりコメント書くなり@Flast_ROにmentionするなりしてください。
あと、間違ってても責任は負いません。面倒です。
(よくここまでズラズラとかけたもんだ...