はじめに
C++ でラムダ式を書いていたらハマったのでメモ.
単刀直入に言うと,局所変数の参照をラムダ式のキャプチャリストに書くな というだけ.
症状
C++ で書いた実行ファイルを,コマンドラインから実行した場合と,Python の subprocess から実行した場合で結果が異なるという現象に遭遇した.
さらに,Debug ビルドと Release ビルドでも結果が異なった.
何事かと思ったら,ラムダ式に問題があった.
原因
ラムダ式を使用する部分で,こんなコードがあった.
std::function<int(int)> f;
if (foo) {
int bar = /* some value */ 0;
f = [&](int a) {
// bar を使った処理
return a + bar;
};
}
if
ブロックの中でローカル変数 bar
を宣言したあと,キャプチャリストに参照 [&]
を指定してからラムダ式内部で bar
を使用している.
当然,if
ブロックを抜けた後は,変数 bar
にはアクセスできなくなるので,ラムダ式実行時に bar
の参照が示す値は未定義となる.
変数 bar
があった場所の値をいつ書き換えるかは OS 次第なので,Debug 時は動作していても Release ビルドすると不具合になったり,他のプロセスから実行すると結果が変わったり,というような再現性のないことになる.
警告も何も出してくれないので要注意.
対処法
キャプチャリストを参照ではなくコピーにするだけ.
f = [=](int a) { // [&] ではなく [=] とする
return a + bar;
};
[bar]
と書いても良いし,他の変数は参照でキャプチャしたい場合は [&, bar]
とも書ける.std::vector
など比較的大きいデータの場合は,なるべく参照でキャプチャしたい.
関連
局所変数の参照という点に関連して,よく問題になるコードがこれ.
int* func() {
int v = 0;
return &v; // ダメ
}
ローカル変数のポインタを返すな,というもの.
こちらの方がいささか有名だが,セットで押さえておきたい.
おわりに
C++ に慣れている人にとっては常識かもしれませんが,自戒のため記事にしました.
ラムダ式をあまり使わない人や,脳死で [&]
を書いてしまう人は留意してください.