Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/158.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
默认情况下,为什么C++11的lambda需要“mutable”关键字来按值捕获?_C++_Lambda_C++11 - Fatal编程技术网

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

默认情况下,为什么C++11的lambda需要“mutable”关键字来按值捕获?,c++,lambda,c++11,C++,Lambda,C++11,简短示例: #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

简短示例:

#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"
}
问题是:为什么我们需要可变关键字?它与传递给命名函数的传统参数有很大不同。背后的理由是什么


我的印象是,按值捕获的整个要点是允许用户更改临时值-否则,使用引用捕获几乎总是更好,不是吗

有什么启示吗

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

参见第5.1.2节【表达式初始值】第5款:

lambda表达式的闭包类型有一个公共内联函数调用运算符13.5.4,其参数 和返回类型由lambda表达式的参数声明子句和trailingreturn描述- 分别输入。当且仅当lambdaexpression 参数声明子句后面没有可变的

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

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


我的印象是,按值捕获的整个要点是允许用户更改临时值-否则,使用引用捕获几乎总是更好,不是吗

问题是,这差不多了吗?一个常见的用例似乎是返回或传递lambda:

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

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

我认为可变不是几乎所有的情况。我考虑值捕获,如允许我在捕获的实体死后使用它的值,而不是允许我更改它的副本。但也许这是有道理的

您需要考虑Lambda函数的闭包类型。每次声明一个Lambda表达式时,编译器都会创建一个闭包类型,它无非是一个带有属性的未命名类声明,其中声明了Lambda表达式并实现了函数call::operator。当您使用copy by value捕获变量时,编译器将在闭包类型中创建一个新的const属性,因此您不能在Lambda表达式中更改它,因为它是只读属性,这就是他们称之为闭包的原因,因为在某种程度上,通过将变量从上限范围复制到Lambda范围中,可以关闭Lambda表达式。使用关键字mutable时,捕获的实体将成为闭包类型的非常量属性。这就是导致值捕获的可变变量中所做的更改不会传播到更高的范围,而是保持在有状态Lambda中的原因。 始终尝试想象Lambda表达式的结果闭包类型,这对我有很大帮助,我希望它也能对您有所帮助。

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

我的印象是 按价值捕获的整个要点是 允许用户更改临时设置 -否则,使用引用捕获几乎总是更好,不是吗 我


n不是临时的。n是使用lambda表达式创建的lambda函数对象的成员。默认情况下,调用lambda不会修改其状态,因此它是常量,以防止您意外修改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视为生成一个带有运算符的类,该运算符默认为const,除非您说它是可变的


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

现在有一个建议来减轻对lambda声明中可变的需求:

fWw,C++标准化委员会的著名成员萨特Hug,在这个问题上提供了一个不同的答案:

考虑这个稻草人的例子,程序员通过 值,并尝试修改 捕获的值是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’
添加此功能似乎是出于用户的担忧 可能没有意识到他得到了一份拷贝,尤其是自从lambdas 他可能是樟宜 拿着另一个兰姆达的副本


他的论文是关于为什么在C++14中应该改变这一点。如果你想知道[委员会成员]对这一特殊功能的想法,那么这本书很短,写得很好,值得一读。

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

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


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

您必须理解捕获的含义!这不是争论!让我们看一些代码示例:

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

}

上面的示例显示,通过使lambda可变,在lambda中更改x会在每次调用时使用与主函数中x的实际值无关的新值x对lambda进行变异

好问题;虽然我很高兴一些东西在默认情况下终于稳定了!这不是一个答案,但我认为这是一件明智的事情:如果您按值获取某个内容,您不应该仅仅为了将1个副本保存到局部变量而对其进行更改。至少你不会因为替换n=.& ..xftl:不确定它是好的,当其他事物都不是默认的const时,你就不会犯错误。@阿纳尔的SZele:不要开始争论,但是IHO容易学习的概念在C++语言中是没有位置的,尤其是在现代。无论如何:p通过值捕获的全部要点是允许用户更改临时值-否,全部要点是lambda可能在任何捕获变量的生存期之后保持有效。如果C++ lambda只有通过REF捕获,那么它们将无法在太多的场景中使用。这是一个非常强大的使用价值捕获的用例。但为什么它默认为const?它达到了什么目的?可变的似乎不合适,当const不是几乎所有的语言中的默认值。@ KIZZX2:我希望const是默认的,至少人们会被迫考虑const正确性:/@ @ kigZx2查看lambda文件,在我看来,他们将其默认为const,这样无论lambda对象是否为const,他们都可以调用它。例如,他们可以将其传递给采用std::function const&的函数。为了允许lambda更改其捕获的副本,在最初的文档中,闭包的数据成员在内部自动定义为可变的。现在必须手动将mutable放入lambda表达式中。不过,我还没有找到详细的理由。有关详细信息,请参阅。在这一点上,对我来说,真正的答案/理由似乎是他们没有解决实现细节:/这是标准,但他们为什么这样写?@kizzx2:我的解释就在这段引文的正下方它与litb所说的捕获对象的生命周期有一点关系,但也有一点更进一步。@Xeo:哦,是的,我错过了:P这也是一个很好地使用“按值捕获”的解释。但为什么默认情况下它应该是常量呢?我已经有了一个新的副本,不让我更改它似乎很奇怪——特别是它基本上没有问题——他们只是想让我添加可变的。我相信有人试图创建一个新的genral函数声明语法,看起来很像一个命名的lambda。它还应该通过默认设置一切为常量来解决其他问题。从未完成,但这些想法在lambda定义上逐渐消失。@kizzx2-如果我们可以重新开始,我们可能会使用var作为关键字,以允许更改,而常量是其他所有内容的默认值。现在我们没有,所以我们不得不接受。在我看来,C++2011的表现相当不错,这是一个很好的观点。我完全同意。不过,在C++0x中,我不太明白默认值如何帮助执行上述操作。考虑我在lambda的接收端,例如我是空fsSTD::函数g。我如何保证g实际上是引用透明的?g的供应商可能已经使用了mutable。所以我不知道。另一方面,如果默认值是non-const,并且人们必须向函数对象添加const而不是mutable,那么编译器实际上可以强制执行const std::function部分,现在f可以
假设G是const,no?@ KiZZX2:在C++中,没有强制执行,只是建议。和往常一样,如果你做了一些愚蠢的文档化的引用透明性要求,然后传递非引用透明函数,你会得到任何结果。这个答案让我大开眼界。以前,我认为在本例中,lambda只会对当前运行的副本进行变异。@ZsoltSzatmari您的评论让我大开眼界了!:-直到我读了你的评论,我才明白这个答案的真正含义。我不同意这个答案的基本前提。C++没有函数的概念,它应该总是在语言中的任何其他地方返回相同的值。作为一个设计原则,我同意这是一个编写函数的好方法,但我认为这并不能作为标准行为的理由。有关于这一点的信息吗?我个人认为这是个坏主意,因为新的任意表达式捕获会使大部分的疼痛点平滑。@ Boovigt是的,这看起来是一个改变的原因。@ BenVoigt,尽管公平,我希望有很多C++开发者不知道可变的甚至是C++中的关键字。它的成员也有临时生命。@Ben:IIRC,我指的是当有人说临时时,我理解它是指未命名的临时对象,lambda本身就是,但它的成员不是。而且从lambda内部来看,lambda本身是否是临时的并不重要。重新阅读该问题,尽管OP在说“临时”时似乎只是想说lambda中的n。虽然这是一个很好的解释,说明了如果将常量或可变lambda实现为等效的用户定义类型,它会是什么样子,但问题如标题所示,OP在注释中详细说明了为什么常量是默认值,所以这不是答案。我比其他人更喜欢你的答案。进一步添加lambda=函数+环境/范围。定义lambda时,选择环境。C++提供了环境的概念,即非可变拷贝、可变拷贝或共享环境。
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

}