为什么C ++ 0x的lambda需要“可变”关键字来进行按值捕获,默认情况下?

简短的例子:

#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关键字? 与传统的parameter passing给命名函数完全不同。 背后的理由是什么?

我的印象是,按价值计算的全部重点是允许用户改变临时的 – 否则我几乎总是使用通过引用来获得更好的效果,不是吗?

任何启示?

(我正在使用MSVC2010.AFAIK这应该是标准的)

它需要mutable因为默认情况下,函数对象每次调用时都会产生相同的结果。 这是一个面向对象的函数和一个使用全局variables的函数的区别。

你的代码几乎等于这个:

 #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" } 

所以你可以把lambda当作用operator()生成一个类,除非你说它是可变的,否则默认为const。

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

我的印象是,按价值计算的全部重点是允许用户改变临时的 – 否则我几乎总是使用通过引用来获得更好的效果,不是吗?

问题是,这是“几乎”? 经常使用的情况似乎是返回或传递lambdas:

 void registerCallback(std::function<void()> f) { /* ... */ } void doSomething() { std::string name = receiveName(); registerCallback([name]{ /* do something with name */ }); } 

我认为mutable不是“几乎”的情况。 我认为“按价值捕捉”就像“让我在被捕获的实体死亡后使用它的价值”而不是“允许我改变它的副本”。 但也许这可以被争辩。

FWIW,Herb Sutter,C ++标准化委员会的着名成员,在Lambda正确性和可用性问题中为这个问题提供了不同的答案:

考虑这个稻草人的例子,程序员通过值捕获一个局部variables,并试图修改捕获的值(这是lambda对象的成员variables):

 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' 

这个function似乎是出于一个担心,用户可能没有意识到他得到了一个副本,特别是由于lambda可复制,他可能会改变一个不同的lambda的副本。

他的论文是关于为什么在C ++ 14中应该改变它。 如果你想了解“委员会成员”关于这个特点的问题,那么这篇文章写得很短,写得很好,值得一读。

见5.1.2 [expr.prim.lambda]子条款5:

lambdaexpression式的闭包types有一个公共的内联函数调用操作符(13.5.4),其参数和返回types分别由lambdaexpression式的parameter-declaration-clause和trailingreturn-type来描述。 这个函数调用操作符被声明为const(9.3.1)当且仅当lambdaexpression式的参数声明子句没有跟随可变。

在litb的评论上编辑:也许他们认为按值捕获,所以variables的外部变化不反映在lambda? 引用是双向的,所以这是我的解释。 不知道是否有好处。

编辑kizzx2的评论:使用lambda的次数最多的是algorithm的函子。 默认的const让它可以在一个常量的环境中使用,就像普通的const限定的函数可以在那里一样,但是非const限定的函数不能。 也许他们只是想让这些情况更直观,知道这些情况。 🙂

您需要考虑Lambda函数的闭包types 。 每次你声明一个Lambdaexpression式时,编译器都会创build一个闭包types,它不过是一个带有属性的匿名类声明(声明了Lambdaexpression式的环境 )和函数call ::operator() 。 当您使用按值复制捕获variables时,编译器将在闭包types中创build一个新的const属性,因此您不能在Lambdaexpression式中更改它,因为它是“只读”属性,所以这是原因他们称之为“ 闭包 ”,因为在某种程度上,通过将variables从上层作用域复制到Lambda作用域中来closuresLambdaexpression式。 当您使用关键字mutable ,捕获的实体将成为您的闭包types的non-const属性。 这是什么原因导致在由值捕获的可变variables中完成的更改不会传播到上方作用域,而是保留在有状态Lambda中。 总是试图想象你的Lambdaexpression式的闭合types,这对我有很大帮助,我希望它也能帮助你。

我的印象是,按价值计算的全部重点是允许用户改变临时的 – 否则我几乎总是使用通过引用来获得更好的效果,不是吗?

n 不是一个临时的。 n是用lambdaexpression式创build的lambda函数对象的成员。 默认的期望是调用你的lambda不会修改它的状态,因此它是const的,以防止你意外地修改n

现在有一个build议,以减轻lambda声明中的mutable : n3424