2013-04-17 10 views
8
#include <iostream> 

void foo(int k) { 
    static auto bar = [&]{ 
     std::cout << k << std::endl; 
    }; 
    bar(); 
} 

int main() { 
    foo(1); foo(2); foo(3); // output is correct: 1, 2, 3 
} 

チェック静的ラムダは参照によりKをキャプチャする方法機能FOO、。これはうまくいくと思われ、intではなく、より複雑なデータ型でも同じことが起こっています。リファレンス

これは予想されますか? kのアドレスは、fooのすべての呼び出しで同じになるという保証はありますか?UBですか?

事前のおかげで、と申し訳ありません、これは以前に(私は成功せずに同様の質問を探してみてくださいでした)

+0

アダムはマイルでポイントを逃した、私は恐れている – sehe

答えて

4

に答えた場合には、未定義の動作です。

関数呼び出し式とそのパラメータの初期化について、標準C++ 11の5.2.2/4段落当たり:ときに関数パラメータの寿命が終了

[...] が定義されている場合は、が返されます。各パラメータの初期化と破棄は、呼び出し関数 のコンテキスト内で発生します。 [...]

したがって、ラムダは、関数呼び出しが返されるとすぐにぶら下がる参照を格納します。

この場合、インプリメンテーションでは、関数呼び出しごとに同じアドレスに関数パラメータを作成するのは無償です(おそらく)。

しかし、この動作は標準によって必須ではありません - したがって、あなたはそれに頼るべきではありません(この場合、あなたのコードは3.8/7のために正当なものになります)。

+0

素晴らしい答え! 3.8/7とは何ですか? –

+1

@JoséManuel: "*オブジェクトの存続期間が終了し、オブジェクトが占有した記憶域が再利用されるか、または が解放される前に、元のオブジェクトが占有した格納場所に新しいオブジェクトが作成されると、 オリジナルオブジェクトを参照する参照、または元のオブジェクトの名前が元のオブジェクトに自動的に参照され、新しいオブジェクトのライフタイムが開始されたら、 を使用して新しいオブジェクトを操作できます、if * [...] " - あなたの例で満たされる条件は次のとおりです。 –

+0

* "実装は、各関数呼び出しで同じアドレスに関数パラメータを作成することは自由です(そしておそらくそうです)" - もちろん、スタックから渡されることを考慮して、トップアドレスは関数が呼び出されるたびに同じであることはほとんどありません)。または、スタックポインタが個別の関数呼び出しの間で変更されないことが実際に*ある可能性が高いという彼の例では意味がありますか? –

1

あなたの例でおそらく "働いている"という理由は、呼び出しスタックが常に同じ方法で並んでいるからです。代わりにこれを試して、まだ "期待される"出力が得られるかどうか確認してください。

#include <iostream> 

void foo(int k) { 
    static auto bar = [&]{ 
     std::cout << k << std::endl; 
    }; 
    bar(); 
} 

void baz(int k) { 
    std::cout << "baz: "; 
    foo(k); 
} 

int main() { 
    foo(1); baz(2); foo(3); 
} 
+0

私はclang3.2とg ++ 4.7.2を使用していますが、あなたのバージョンもあまりにも「うまくいく」ようです。しかし、私はちょうどあなたの* foo(k)*を* foo(k + 1)*に置き換えようとしました。そして今、私は2つのコンパイラから異なる出力を持っています:)(clang:1,2,3、gcc:1 、3、3) –

+1

ポイントはあなたが使用しているコンパイラではありません。ポイントは動作が定義されていないことです。つまり、場合によっては動作し、別の場合にはクラッシュする可能性があります。さらに悪いことに、あなたのハードドライブをフォーマットしたり、酸素供給を停止したり、他の損害を引き起こす可能性があります。 –

+1

これを機能させるには、[もっと2つのパラメータを追加する]必要があると思います(http://ideone.com/3B37yt)。 –