【问题标题】:Why capture lambda does not working in c++?为什么捕获 lambda 在 C++ 中不起作用?
【发布时间】:2021-10-07 19:13:42
【问题描述】:

我正在使用 C++ 中的 lambda 表达式,并尝试了一些方法来查看结果。我实际上在 CppCon Back to Basics: Lambdas from Scratch - Arthur O'Dwyer - CppCon 2019 @21:47 观看了视频并开始使用 lambdas。

举个例子,我试过这个:

#include <iostream>
using namespace std;
int g = 10;//global var 'g'

//creating lambda
auto kitten = [=] () {return g+1;};
auto cat = [g=g] () {return g+1;};
// main
int main()
{
    g = 20;//modifying global variable 'g'
    cout<<"kitten: "<<kitten()<<"cat: "<<cat()<<endl;

    return 0;
}

以上代码的输出为:

kitten: 21cat: 11

在上面的例子中:[g=g]表示捕获一个名称为g且类型与外部g相同的数据成员,就像我写了auto g=g一样。这是g 的副本。当我们认为时这是有道理的(好像我以auto g=g 的形式写的)所以在我们的例子中结果是11,全局g 的修改不会反映在我们的local g 中。

小猫的结果是 21,因为据我了解,捕获所有内容,即按值捕获所有外部变量。

然后,当涉及到这个例子时,通过修改第一个 lambda 如下:

auto kitten = [] () {int g  = g; return g+1;};

在我声明 local g 并从 global g 赋值的地方,输出是:

kitten: 1cat: 11

但我期待第一个示例 (21) 中的输出,因为我正在尝试创建一个本地 g 并从全局 g 分配它的值,它已经是修改后的值 20。

代码在https://techiedelight.com/compiler/和godbolt.org上用c++(GCC 8.3.0)编译(用最新的编译器[=]这是不允许的,但结果是一样的)。

此时,我对捕获和/或 lambda 的概念有点困惑。

【问题讨论】:

  • [g=g][auto g=g] 相同,并且是您正在执行的操作。 = 表示在 lambda 捕获中复制,&amp; 表示引用。
  • @NathanOliver 除了[auto g=g] 不是真正的语法。 (如果真的需要在初始化捕获上强制使用一种类型,可以使用 [v=static_cast&lt;T&gt;(e)]。)
  • @Yakk - Adam Nevraumont 它可以从这里编译:techiedelight.com/compiler 使用 gcc 8.3.0,如帖子中所述!请仔细阅读并在那里尝试。
  • 除非捕获列表为空并且 lambda 表达式的主体中没有引用外部非 constexpr 符号,否则此 lambda 会执行捕获。无捕获 lambda 表达式的一个可靠标志是它可以绑定到函数指针。

标签: c++ c++11 lambda std capture


【解决方案1】:
auto kitten = [] () {int g  = g; return g+1;};

您在这里根本没有使用全局变量。您正在使用本地 g 来初始化本地 g。程序的行为未定义。

为什么int g = g;试图自己初始化本地g,

因为初始化器位于声明本地g 的位置之后。

那个编译器不应该用全局 g 初始化吗?

没有。

【讨论】:

  • 也许提出如何解决全球问题的建议:auto kitten = [] () {int g = ::g; return g+1;};
  • @eerorika 为什么会这样?我们可以在另一个函数中创建一个同名的局部变量,并且在主函数中我们可以再次访问全局g。我的观点是为什么 int g = g;试图自己初始化本地g,编译器不应该用全局g初始化吗?从右到左?例如:调用函数 foo() 和内部 foo() int g= 33 将是本地的,在调用 foo() 之后的 main 中,全局 g 的输出仍然保持不变?访问全局变量没有问题。在另一个函数内部,但为什么是这个 init。有问题吗?
  • @Ted Lyngmo,是的,你是对的,当我们使用 :: 运算符时,它按预期工作!但我认为,默认情况下,编译器应该这样做。如前文所述,从右到左...
【解决方案2】:
auto kitten = [=] () {return g+1;}

这个 lambda 根本不捕获任何东西。和刚才的差不多

int kitten() { return g+1; }

只能捕获局部变量,kitten 定义范围内没有可见的局部变量。请注意,[=][&amp;] 并不意味着“捕获一切”,它们的意思是“捕获任何必要的东西”,并且在 lambda 中永远不需要(或可能)捕获全局变量,因为该变量名称的含义无论何时评估 lambda 主体,都始终相同。


auto cat = [g=g] () {return g+1;}

这是一个init-capture,类似于创建一个局部变量并立即捕获它。等号之前的g 声明了初始化捕获,等号之后的g 指定了如何初始化它。与大多数声明符(见下文)不同,此处创建的g 变量不在其自己的初始化程序的范围内,因此等号后的g 表示全局变量::g。所以代码类似于:

auto make_cat()
{
    int & g = ::g;
    return [g]() { return g+1; }
}
auto cat = make_cat();

auto kitten = [] () {int g  = g; return g+1;}

这段代码有一个与 lambda 无关的错误。在局部变量定义int g = g; 中,等号之前声明的变量在等号之后的初始化程序期间处于作用域内。所以g 被初始化为它自己的不确定值。向该不确定值加 1 是未定义的行为,因此结果不可预测。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-11
    • 2011-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多