2012-04-09 7 views
4

標準では、このコードで何が起こるかを定義していますか?C++のラムダ式で値を取得するには、ラムダオブジェクトで値をコピーする必要がありますか?

#include <iostream> 

template <typename Func> 
void callfunc(Func f) 
{ 
    ::std::cout << "In callfunc.\n"; 
    f(); 
} 

template <typename Func> 
void callfuncref(Func &f) 
{ 
    ::std::cout << "In callfuncref.\n"; 
    f(); 
} 

int main() 
{ 
    int n = 10; 
    // n is captured by value, and the lambda expression is mutable so 
    // modifications to n are allowed inside the lambda block. 
    auto foo = [n]() mutable -> void { 
     ::std::cout << "Before increment n == " << n << '\n'; 
     ++n; 
     ::std::cout << "After increment n == " << n << '\n'; 
    }; 
    callfunc(foo); 
    callfunc(foo); 
    callfuncref(foo); 
    callfunc(foo); 
    return 0; 
} 

グラムの++とこれの出力は次のようになります。

$ ./a.out 
In callfunc. 
Before increment n == 10 
After increment n == 11 
In callfunc. 
Before increment n == 10 
After increment n == 11 
In callfuncref. 
Before increment n == 10 
After increment n == 11 
In callfunc. 
Before increment n == 11 
After increment n == 12 

は標準で必要とされるこの出力のすべての機能はありますか?

特に、ラムダオブジェクトのコピーが作成された場合、取得されたすべての値もコピーされるように見えます。しかし、ラムダオブジェクトが参照渡しされた場合、取得された値はコピーされません。また、関数が呼び出される直前にはキャプチャされた値のコピーが作成されないので、キャプチャされた値への変更がコール間で保持されます。

答えて

9

ラムダの型は、本体(/ 5)と暗黙のコピーコンストラクタ(/ 19)を実行するoperator()と単純なクラス(n3290§5.1.2/ 3)であり、 copyは、このクラスの非静的データメンバ(/ 14)にcopy-initialize(/ 21)し、その変数の各使用が対応するデータメンバ(/ 17)に置き換えられます。この変換の後、ラムダ式はこのクラスのインスタンスにのみなり、C++の一般的な規則が従います。意味

は、あなたのコードは、同じように動作しなければならない。

int main() 
{ 
    int n = 10; 

    class __Foo__   // §5.1.2/3 
    { 
     int __n__;   // §5.1.2/14 
    public: 
     void operator()() // §5.1.2/5 
     { 
      std::cout << "Before increment n == " << __n__ << '\n'; 
      ++ __n__;  // §5.1.2/17 
      std::cout << "After increment n == " << __n__ << '\n'; 
     } 
     __Foo__() = delete; 
     __Foo__(int n) : __n__(n) {} 
     //__Foo__(const __Foo__&) = default; // §5.1.2/19 
    } 
    foo {n};    // §5.1.2/21 

    callfunc(foo); 
    callfunc(foo); 
    callfuncref(foo); 
    callfunc(foo); 
} 

そして、ここで何をするかcallfuncref明らかです。

+0

これらのラムダ相当のクラスには、コンストラクタの移動と割り当て操作の移動もありますか? – Omnifarious

+3

@Omnifarious:コンストラクタを移動する:「たぶん」(5.1.2/19:「暗黙的に宣言された移動コンストラクタがあるかもしれません)」ラムダには代入演算子がなく、削除されます。 – kennytm

2

明示的にすべてをキャプチャしない限り、値はコピーによってキャプチャされます。すべて[&]または参照番号[&n]で特定の変数をキャプチャします。したがって、出力全体が標準です。

+0

私は特に 'callfuncref'に興味があります。これは、値または参照によるキャプチャがデフォルトであるかどうかよりも、キャプチャされた値の変更可能性に関するものです。 – Omnifarious

4

ラムダを構造体/クラスに手動で拡張することで、この動作を理解するのが最も簡単です(nは値によってキャプチャされるため、参照によってキャプチャが少し違うように見えます)。

class SomeTemp { 
    mutable int n; 
    public: 
    SomeTemp(int n) : n(n) {} 
    void operator()() const 
    { 
     ::std::cout << "Before increment n == " << n << '\n'; 
     ++n; 
     ::std::cout << "After increment n == " << n << '\n'; 
    } 
} foo(n); 

あなたの機能callfunccallfuncrefは、この型のオブジェクトの詳細以下を操作します。今、私たちは、あなたが電話を調べてみましょう:

ここでは、値渡し
callfunc(foo); 

は、そうfooはデフォルトのコピーコンストラクタを使用してコピーされます。 callfuncの操作はコピーされた値の内部状態にのみ影響し、実際のfooオブジェクトでは状態は変更されません。

callfunc(foo); 

同じもの

callfuncref(foo); 

ああ、ここでは参照によってFOOを渡すので、(operator()を呼び出す)callfuncrefは、実際のfooオブジェクトではなく、一時的なコピーを更新します。これによりfoonがその後11に更新されます。これは通常のpass-by-referenceの動作です。そのため、あなたは再びこれを呼び出すとき:

callfunc(foo); 

をもう一度コピー上で動作しますが、nが11に設定されているfooのコピーは、あなたが期待するもの示しています。

+0

私は、標準が言ったことを本当に心配していなかった別の質問で、あなたの答えをより良い答えと考えていたでしょう。 :-) – Omnifarious

関連する問題