默认情况下,为什么 C++11 的 lambda 需要“可变”关键字才能按值捕获?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5501959/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 18:17:53  来源:igfitidea点击:

Why does C++11's lambda require "mutable" keyword for capture-by-value, by default?

c++lambdac++11

提问by kizzx2

Short example:

简短示例:

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

The question: Why do we need the mutablekeyword? It's quite different from traditional parameter passing to named functions. What's the rationale behind?

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

I was under the impression that the whole point of capture-by-value is to allow the user to change the temporary -- otherwise I'm almost always better off using capture-by-reference, aren't I?

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

Any enlightenments?

有什么启示吗?

(I'm using MSVC2010 by the way. AFAIK this should be standard)

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

采纳答案by Puppy

It requires mutablebecause by default, a function object should produce the same result every time it's called. This is the difference between an object orientated function and a function using a global variable, effectively.

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

回答by Daniel Munoz

Your code is almost equivalent to this:

您的代码几乎等同于:

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

So you could think of lambdas as generating a class with operator() that defaults to const unless you say that it is mutable.

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

You can also think of all the variables captured inside [] (explicitly or implicitly) as members of that class: copies of the objects for [=] or references to the objects for [&]. They are initialized when you declare your lambda as if there was a hidden constructor.

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

回答by Johannes Schaub - litb

I was under the impression that the whole point of capture-by-value is to allow the user to change the temporary -- otherwise I'm almost always better off using capture-by-reference, aren't I?

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

The question is, is it "almost"? A frequent use-case appears to be to return or pass lambdas:

问题是,是“几乎”吗?一个常见的用例似乎是返回或传递 lambdas:

void registerCallback(std::function<void()> f) { /* ... */ }

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

I think that mutableisn't a case of "almost". I consider "capture-by-value" like "allow me to use its value after the captured entity dies" rather than "allow me to change a copy of it". But perhaps this can be argued.

我认为这mutable不是“几乎”的情况。我认为“按值捕获”类似于“允许我在捕获的实体死亡后使用其值”而不是“允许我更改它的副本”。但也许这是可以争论的。

回答by akim

FWIW, Herb Sutter, a well-known member of the C++ standardization committee, provides a different answer to that question in Lambda Correctness and Usability Issues:

FWIW,C++ 标准化委员会的知名成员 Herb Sutter 在Lambda Correctness and Usability Issues 中对这个问题提供了不同的答案:

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.

考虑这个稻草人示例,其中程序员按值捕获局部变量并尝试修改捕获的值(这是 lambda 对象的成员变量):

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'

添加此功能似乎是出于担心用户可能没有意识到他获得了副本,特别是由于 lambda 是可复制的,因此他可能正在更改不同的 lambda 副本。

His paper is about why this should be changed in C++14. It is short, well written, worth reading if you want to know "what's on [committee member] minds" with regards to this particular feature.

他的论文是关于为什么应该在 C++14 中改变这一点。如果您想了解有关此特定功能的“[委员会成员] 的想法”,那么它很短,写得很好,值得一读。

回答by Tarantula

You need to think what is the closure typeof your Lambda function. Every time you declare a Lambda expression, the compiler creates a closure type, which is nothing less than an unnamed class declaration with attributes (environmentwhere the Lambda expression where declared) and the function call ::operator()implemented. When you capture a variable using copy-by-value, the compiler will create a new constattribute in the closure type, so you can't change it inside the Lambda expression because it is a "read-only" attribute, that's the reason they call it a "closure", because in some way, you are closing your Lambda expression by copying the variables from upper scope into the Lambda scope. When you use the keyword mutable, the captured entity will became a non-constattribute of your closure type. This is what causes the changes done in the mutable variable captured by value, to not be propagated to upper scope, but keep inside the stateful Lambda. Always try to imagine the resulting closure type of your Lambda expression, that helped me a lot, and I hope it can help you too.

您需要考虑Lambda 函数的闭包类型是什么。每次声明 Lambda 表达式时,编译器都会创建一个闭包类型,它只不过是一个带有属性(声明Lambda 表达式的环境)和函数调用::operator()实现的未命名类声明。当您使用copy-by-value捕获变量时,编译器将const在闭包类型中创建一个新属性,因此您无法在 Lambda 表达式中更改它,因为它是“只读”属性,这就是它们的原因称之为“闭包”,因为在某种程度上,您通过将变量从上层范围复制到 Lambda 范围来关闭 Lambda 表达式。mutable,捕获的实体将成为non-const您的闭包类型的属性。这就是导致在由值捕获的可变变量中所做的更改不会传播到上层作用域,而是保留在有状态的 Lambda 中的原因。总是试着想象你的 Lambda 表达式的结果闭包类型,这对我有很大帮助,我希望它也能帮助你。

回答by Xeo

See this draft, under 5.1.2 [expr.prim.lambda], subclause 5:

请参阅此草案,在 5.1.2 [expr.prim.lambda] 下的第 5 款:

The closure type for a lambda-expression has a public inline function call operator (13.5.4) whose parameters and return type are described by the lambda-expression's parameter-declaration-clause and trailingreturn- type respectively. This function call operator is declared const (9.3.1) if and only if the lambdaexpression's parameter-declaration-clause is not followed by mutable.

lambda 表达式的闭包类型有一个公共内联函数调用运算符 (13.5.4),其参数和返回类型分别由 lambda 表达式的参数声明子句和尾随返回类型描述。此函数调用运算符声明为 const (9.3.1) 当且仅当 lambda 表达式的参数声明子句后面没有可变的。

Edit on litb's comment: Maybe they thought of capture-by-value so that outside changes to the variables aren't reflected inside the lambda? References work both ways, so that's my explanation. Don't know if it's any good though.

编辑 litb 的评论:也许他们想到了按值捕获,以便变量的外部更改不会反映在 lambda 内部?参考文献是双向的,所以这就是我的解释。不过不知道好不好用。

Edit on kizzx2's comment: The most times when a lambda is to be used is as a functor for algorithms. The default constness lets it be used in a constant environment, just like normal const-qualified functions can be used there, but non-const-qualified ones can't. Maybe they just thought to make it more intuitive for those cases, who know what goes on in their mind. :)

编辑 kizzx2 的评论:使用 lambda 的最多次数是作为算法的函子。默认的constness 允许它在一个恒定的环境中使用,就像普通的const-qualified 函数可以在那里使用一样,但 non- constqualified 的不能。也许他们只是想让这些案例更直观,谁知道他们在想什么。:)

回答by Martin Ba

I was under the impression that the whole point of capture-by-value is to allow the user to change the temporary -- otherwise I'm almost always better off using capture-by-reference, aren't I?

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

nis nota temporary. n is a member of the lambda-function-object that you create with the lambda expression. The default expectation is that calling your lambda does not modify its state, therefore it is const to prevent you from accidentally modifying n.

n不是暂时的。n 是您使用 lambda 表达式创建的 lambda 函数对象的成员。默认期望是调用 lambda 不会修改其状态,因此 const 可以防止您意外修改n.

回答by Soulimane Mammar

You have to understand what capture means! it's capturing not argument passing! let's look at some code samples:

您必须了解捕获的含义!它捕获而不是参数传递!让我们看一些代码示例:

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

}

As you can see even though xhas been changed to 20the lambda is still returning 10 ( xis still 5inside the lambda) Changing xinside the lambda means changing the lambda itself at each call (the lambda is mutating at each call). To enforce correctness the standard introduced the mutablekeyword. By specifying a lambda as mutable you are saying that each call to the lambda could cause a change in the lambda itself. Let see another example:

正如您所看到的,即使x已更改为20lambda 仍然返回 10(x仍在5lambdax内)在 lambda 内更改意味着在每次调用时更改 lambda 本身(lambda 在每次调用时都会发生变化)。为了强制执行正确性,标准引入了mutable关键字。通过将 lambda 指定为可变的,您是说对 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

}

The above example shows that by making the lambda mutable, changing xinside the lambda "mutates" the lambda at each call with a new value of xthat has no thing to do with the actual value of xin the main function

上面的示例表明,通过使 lambda 可变,x在每次调用时更改lambda 内部的 lambdax会使用与x主函数中的实际值无关的新值

回答by usta

There is now a proposal to alleviate the need for mutablein lambda declarations: n3424

现在有一个建议可以减少mutablelambda 声明的需要:n3424

回答by Attersson

To extend Puppy's answer, lambda functions are intended to be pure functions. That means every call given a unique input set always returns the same output. Let's define inputas the set of all arguments plus all captured variables when the lambda is called.

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

In pure functions output solely depends on input and not on some internal state. Therefore any lambda function, if pure, does not need to change its state and is therefore immutable.

在纯函数中,输出仅取决于输入而不取决于某些内部状态。因此,任何 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.

当 lambda 通过引用捕获时,写入捕获的变量是对纯函数概念的一种压力,因为纯函数应该做的就是返回一个输出,尽管 lambda 肯定不会发生变异,因为写入发生在外部变量上。即使在这种情况下,正确的用法也意味着如果再次使用相同的输入调用 lambda,输出将每次都相同,尽管对 by-ref 变量有这些副作用。这种副作用只是返回一些额外输入(例如更新计数器)的方法,可以重新表述为纯函数,例如返回元组而不是单个值。