短的例子:

#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。这应该是标准的)


当前回答

为了扩展Puppy的回答,lambda函数被设计为纯函数。这意味着给定唯一输入集的每次调用总是返回相同的输出。让我们将input定义为调用lambda时所有参数加上所有捕获变量的集合。

在纯函数中,输出完全依赖于输入,而不依赖于某些内部状态。因此,任何lambda函数,如果是纯的,不需要改变其状态,因此是不可变的。

When a lambda captures by reference, writing on captured variables is a strain on the concept of pure function, because all a pure function should do is return an output, though the lambda does not certainly mutate because the writing happens to external variables. Even in this case a correct usage implies that if the lambda is called with the same input again, the output will be the same everytime, despite these side effects on by-ref variables. Such side effects are just ways to return some additional input (e.g. update a counter) and could be reformulated into a pure function, for example returning a tuple instead of a single value.

其他回答

FWIW, c++标准化委员会的知名成员Herb Sutter在Lambda正确性和可用性问题中给出了不同的答案:

Consider this straw man example, where the programmer captures a local variable by value and tries to modify the captured value (which is a member variable of the lambda object): int val = 0; auto x = [=](item e) // look ma, [=] means explicit copy { use(e,++val); }; // error: count is const, need ‘mutable’ auto y = [val](item e) // darnit, I really can’t get more explicit { use(e,++val); }; // same error: count is const, need ‘mutable’ This feature appears to have been added out of a concern that the user might not realize he got a copy, and in particular that since lambdas are copyable he might be changing a different lambda’s copy.

他的论文是关于为什么在c++ 14中应该改变这一点。它很短,写得很好,如果你想知道关于这个特定的特性“委员们在想什么”,值得一读。

你的代码几乎相当于:

#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时被初始化,就像有一个隐藏的构造函数一样。

它需要mutable,因为默认情况下,函数对象每次调用都应该产生相同的结果。这就是面向对象的函数和使用全局变量的函数之间的区别。

参见本草案,在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限定的函数不能。也许他们只是想让这些情况更直观,他们知道自己在想什么。:)

你必须理解捕获的意思!这是捕获而不是参数传递!让我们看一些代码示例:

int main()
{
    using namespace std;
    int x = 5;
    int y;
    auto lamb = [x]() {return x + 5; };

    y= lamb();
    cout << y<<","<< x << endl; //outputs 10,5
    x = 20;
    y = lamb();
    cout << y << "," << x << endl; //output 10,20

}

正如你所看到的,即使x被更改为20,仍然返回10 (x在lambda中仍然是5) 在lambda内部更改x意味着在每次调用中更改lambda本身(lambda在每次调用中都发生突变)。为了加强正确性,标准引入了mutable关键字。通过将lambda指定为mutable,就意味着对lambda的每次调用都可能导致lambda本身的更改。让我们看另一个例子:

int main()
{
    using namespace std;
    int x = 5;
    int y;
    auto lamb = [x]() mutable {return x++ + 5; };

    y= lamb();
    cout << y<<","<< x << endl; //outputs 10,5
    x = 20;
    y = lamb();
    cout << y << "," << x << endl; //outputs 11,20

}

上面的例子表明,通过使lambda可变,在lambda内部改变x会在每次调用时用一个新的x值“突变”lambda,而这个x值与主函数中x的实际值没有任何关系