何时解析 C++11 lambda 表达式中的变量引用

When is a variable reference in a C++11 lambda expression resolved?

本文关键字:变量 引用 表达式 C++11 lambda 何时解      更新时间:2023-10-16

我有一个(希望)关于lambda表达式的简单问题:

#include <vector>
#include <algorithm>
//----------------------------------------------------------------
void DoSomething()
//----------------------------------------------------------------
{
  std::vector<int> elements;
  elements.push_back(1);
  elements.push_back(2);
  int ref = 1;
  auto printhit = [=](int iSomeNumber)
  {
    if (ref == iSomeNumber)
    {
      printf("Hit: %dn", iSomeNumber);
    }
    else
    {
      printf("No Hit: %dn", iSomeNumber);
    }
  };
  ref = 2;
  std::for_each(elements.begin(), elements.end(), printhit);   
}

现在,我的问题是:当我使用捕获 [=] 定义 printhit 时,它会打印"命中:1"。如果我通过引用 [&] 传递它,它会打印"命中:2"。我不知何故期望,替换是在for_each内完成的,因此无论我如何授予对"ref"的访问权限,都会打印"Hit:2"。

谁能向我解释一下?

谢谢 马库斯

捕获发生在您声明 lambda 的位置。 就像在此时创建一个类对象并将其ref传递给其构造函数一样。

您的示例等效于以下内容:

class Functor
{
public:
    Functor(int r) :ref(r) {}
    void operator()(int iSomeNumber) const
    {
        if (ref == iSomeNumber)
        {
            printf("Hit: %dn", iSomeNumber);
        }
        else
        {
            printf("No Hit: %dn", iSomeNumber);
        }    
    }
private:
    int ref;
};
void DoSomething()
//----------------------------------------------------------------
{
  std::vector<int> elements;
  elements.push_back(1);
  elements.push_back(2);
  int ref = 1;
  Functor printhit(ref);
  ref = 2;
  std::for_each(elements.begin(), elements.end(), printhit);   
}

我猜,C++标准的以下部分适用:

5.1.2.14:
如果实体是隐式捕获的,并且捕获默认值为 =,或者如果它是显式的,则通过 copy 捕获实体使用不包含 &.对于 copy 捕获的每个实体,一个未命名的非静态数据成员在闭包类型中声明。未指定这些成员的声明顺序。如果此类实体不是引用对象,否则引用的类型。[ 注意:如果捕获的实体是对函数,对应的数据成员也是对函数的引用。—尾注 ]
5.1.2.21:
计算 lambda 表达式时,使用 copy 捕获的实体进行直接初始化生成的闭包对象的每个对应的非静态数据成员。(对于数组成员,数组元素按下标顺序递增直接初始化。这些初始化在(未指定)声明非静态数据成员的顺序。[ 注意:这可确保破坏将按与构造相反的顺序进行。—尾注 ]

让它们都以相同的方式操作有什么意义? [=]的要点是支持通过复制而不是通过引用捕获。

想象一下,如果[=]不可用:如果您在定义 lambda 的代码点知道运行时值,并希望 lambda 此后使用它,那么该值如何可用于 lambda 代码? 当 DoSomething() 运行 by-ref 时,[&]访问其局部 ref 变量可能会起作用,但是如果您希望 lambda 的生命周期在包含它的DoSomething()中超过本地范围的寿命,或者想要更改 ref 的值而不影响将来对 lambda 的调用,该怎么办? 从概念上讲,你可以让语言禁止所有这些事情(在更改ref或更改ref后使用,或者在更改ref超出范围后调用 lambda),或者程序员可以不遗余力地将ref的值放在某个地方供 lambda 使用(例如,在堆上, 需要管理释放,或者在某些具有重新进入和线程安全问题的静态缓冲区中),但为了方便起见,该语言提供了[=]。 编译器生成的 lambda 有效地负责存储和破坏/解除分配ref的副本。