短的例子:

#include <iostream>

int main()
{
    int n;
    [&](){n = 10;}();             // OK
    [=]() mutable {n = 20;}();    // OK
    // [=](){n = 10;}();          // Error: a by-value capture cannot be modified in a non-mutable lambda
    std::cout << n << "\n";       // "10"
}

问题是:为什么我们需要mutable关键字?它与传统的参数传递到命名函数有很大不同。背后的原理是什么?

在我的印象中,按值捕获的全部意义在于允许用户更改临时对象——否则我几乎总是使用按引用捕获更好,不是吗?

有什么启示吗?

(顺便说一下,我用的是MSVC2010。这应该是标准的)


当前回答

你的代码几乎相当于:

#include <iostream>

class unnamed1
{
    int& n;
public:
    unnamed1(int& N) : n(N) {}

    /* OK. Your this is const but you don't modify the "n" reference,
    but the value pointed by it. You wouldn't be able to modify a reference
    anyway even if your operator() was mutable. When you assign a reference
    it will always point to the same var.
    */
    void operator()() const {n = 10;}
};

class unnamed2
{
    int n;
public:
    unnamed2(int N) : n(N) {}

    /* OK. Your this pointer is not const (since your operator() is "mutable" instead of const).
    So you can modify the "n" member. */
    void operator()() {n = 20;}
};

class unnamed3
{
    int n;
public:
    unnamed3(int N) : n(N) {}

    /* BAD. Your this is const so you can't modify the "n" member. */
    void operator()() const {n = 10;}
};

int main()
{
    int n;
    unnamed1 u1(n); u1();    // OK
    unnamed2 u2(n); u2();    // OK
    //unnamed3 u3(n); u3();  // Error
    std::cout << n << "\n";  // "10"
}

因此,您可以将lambdas视为生成一个带有operator()的类,该类默认为const,除非您说它是可变的。

您还可以将[]中捕获的所有变量(显式或隐式)视为该类的成员:[=]对象的副本或[&]对象的引用。它们在声明lambda时被初始化,就像有一个隐藏的构造函数一样。

其他回答

如果你检查lambda的3个不同用例,你可能会发现区别:

按值捕获参数 使用'mutable'关键字按值捕获参数 通过引用捕获参数

案例1: 当你通过值捕获一个参数时,会发生一些事情:

不允许修改lambda内部的参数 无论lambda是什么,参数的值都保持不变 调用时,不管调用时的参数值是什么。

例如:

{
    int x = 100;
    auto lambda1 = [x](){
      // x += 2;  // compile time error. not allowed
                  // to modify an argument that is captured by value
      return x * 2;
    };
    cout << lambda1() << endl;  // 100 * 2 = 200
    cout << "x: " << x << endl; // 100

    x = 300;
    cout << lambda1() << endl;   // in the lambda, x remain 100. 100 * 2 = 200
    cout << "x: " << x << endl;  // 300

}

Output: 
200
x: 100
200
x: 300

案例2: 在这里,当您通过值捕获参数并使用'mutable'关键字时,与第一种情况类似,您创建了该参数的“副本”。这个“副本”存在于lambda的“世界”中,但是现在,你实际上可以在lambda世界中修改参数,因此它的值会被改变,并保存,以便在将来调用这个lambda时引用。同样,参数的外部“生命”可能完全不同(就值而言):

{
    int x = 100;
    auto lambda2 = [x]() mutable {
      x += 2;  // when capture by value, modify the argument is
               // allowed when mutable is used.
      return x;
    };
    cout << lambda2() << endl;  // 100 + 2 = 102
    cout << "x: " << x << endl; // in the outside world - x remains 100
    x = 200;
    cout << lambda2() << endl;  // 104, as the 102 is saved in the lambda world.
    cout << "x: " << x << endl; // 200
}

Output:
102
x: 100
104
x: 200

案例3: 这是最简单的情况,因为x不再有两次生命。现在x只有一个值,它在外部世界和lambda世界之间共享。

{
    int x = 100;
    auto lambda3 = [&x]() mutable {
        x += 10;  // modify the argument, is allowed when mutable is used.
        return x;
    };
    cout << lambda3() << endl;  // 110
    cout << "x: " << x << endl; // 110
    x = 400;
    cout << lambda3() << endl;  // 410.
    cout << "x: " << x << endl; // 410
}

Output: 
110
x: 110
410
x: 410

参见本草案,在5.1.2下[expr.prim.],第5款:

lambda表达式的闭包类型有一个公共内联函数调用操作符(13.5.4),其参数 和返回类型由lambda表达式的参数声明子句和trailingreturn-描述 类型分别。此函数调用操作符声明为const(9.3.1)当且仅当lambdaexpression为 Parameter-declaration-clause后面不跟mutable。

编辑litb的评论: 也许他们想到了按值捕获,这样外部对变量的更改就不会反映在lambda中?引荐是双向的,这就是我的解释。但我不知道这是否有用。

编辑kizzx2的评论: 使用lambda的大多数情况下是作为算法的函子。默认的constness允许它在常量环境中使用,就像普通的const限定函数可以在那里使用,但非const限定的函数不能。也许他们只是想让这些情况更直观,他们知道自己在想什么。:)

现在有一个建议可以减轻lambda声明中对mutable的需求:n3424

我的印象是 价值捕获的关键在于 允许用户更改临时 —否则,我几乎总是更好地使用引用捕获,而不是 我吗?

N不是暂时的。N是使用lambda表达式创建的lambda-function-对象的成员。默认的期望是,调用你的lambda不会修改它的状态,因此它是const,以防止你意外地修改n。

你的代码几乎相当于:

#include <iostream>

class unnamed1
{
    int& n;
public:
    unnamed1(int& N) : n(N) {}

    /* OK. Your this is const but you don't modify the "n" reference,
    but the value pointed by it. You wouldn't be able to modify a reference
    anyway even if your operator() was mutable. When you assign a reference
    it will always point to the same var.
    */
    void operator()() const {n = 10;}
};

class unnamed2
{
    int n;
public:
    unnamed2(int N) : n(N) {}

    /* OK. Your this pointer is not const (since your operator() is "mutable" instead of const).
    So you can modify the "n" member. */
    void operator()() {n = 20;}
};

class unnamed3
{
    int n;
public:
    unnamed3(int N) : n(N) {}

    /* BAD. Your this is const so you can't modify the "n" member. */
    void operator()() const {n = 10;}
};

int main()
{
    int n;
    unnamed1 u1(n); u1();    // OK
    unnamed2 u2(n); u2();    // OK
    //unnamed3 u3(n); u3();  // Error
    std::cout << n << "\n";  // "10"
}

因此,您可以将lambdas视为生成一个带有operator()的类,该类默认为const,除非您说它是可变的。

您还可以将[]中捕获的所有变量(显式或隐式)视为该类的成员:[=]对象的副本或[&]对象的引用。它们在声明lambda时被初始化,就像有一个隐藏的构造函数一样。