2014年4月18日金曜日

C++のlambdaと変数のキャプチャ

覚え書き。

C++のlambdaでは、lambda記述した位置のスコープの変数にlambdaで定義したメソッドの中からアクセスすることができる。ただし、デフォルトでは無効なので、lambdaの外の変数をキャプチャすることを明示しなければならない。以下、サンプルコード

#include <iostream>
#include <functional>
int main(int argc, char *argv[]) {
    std::string msg = "Hello";
    // ERROR : not captured
//  std::function<void()> f0 = []() {
//      std::cout << msg << std::endl;
//  };
//  f0();
    // refer copied msg
    std::function<void()> f1 = [=]() {
        std::cout << msg << std::endl;
    };
    f1();
    msg += " World";
    f1();   // copied msg is not modified
    // refer original msg via reference
    std::function<void()> f2 = [&]() {
        msg += "!";
        std::cout << msg << std::endl;
    };
    f2();
    f2();
    f2();
    // msg is modified by f2()
    std::cout << msg << std::endl;
    return 0;
}

[=]で変数をキャプチャする場合、lambdaで関数オブジェクトを作成するときに、その変数が値渡しされる。なので、関数オブジェクト作成後に元の変数が変更されても、既にコピー済みの値を見ているlambdaの中には影響がない。

[&]で変数をキャプチャした場合、lambdaで関数オブジェクトを作成するときに、その変数が参照渡しされる。なので、関数オブジェクト作成後に元の変数が変更されれば、lambdaの中も影響を受ける。また、lambdaの中でキャプチャした変数を変更すると、lambdaの外も影響を受ける。

ちなみにlambdaを使わないで書くと、以下のような感じになる。

#include <iostream>
int main(int argc, char *argv[]) {
    std::string msg = "Hello";
    // refer copied msg
    struct F1 {
        const std::string msg;
        F1(std::string& msg) : msg(msg) {}
        void operator()() {
            std::cout << msg << std::endl;
        }
    } f1(msg);
    f1();
    msg += "World";
    f1();   // copied msg is not modified
    // refer original msg via reference
    struct F2 {
        std::string& msg;
        F2(std::string& msg) : msg(msg) {}
        void operator()() {
            msg += "!";
            std::cout << msg << std::endl;
        }
    } f2(msg);
    f2();
    f2();
    f2();
    // msg is modified by f2()
    std::cout << msg << std::endl;
    return 0;
}

テンプレートしかり、lambdaしかり、記述が楽になるのは間違いないけど、あくまで人間の代わりにコンパイラが書いてくれるだけなので、コンパイラがどんなコードを吐くのか考えて使う必要はあるね。

0 件のコメント:

コメントを投稿