【问题标题】:Why won't this lambda capture this variable with a deleted constructor by reference?为什么这个 lambda 不能通过引用使用已删除的构造函数来捕获这个变量?
【发布时间】:2020-10-21 14:36:04
【问题描述】:

我的编译器 Visual Studio 19 显示“尝试引用已删除的函数”:

class AClass 
{public:
    AClass(const AClass& other) = delete;
    AClass() {}
    AClass(AClass&& other) { }
    AClass& operator=(const AClass& other) = delete;
    AClass& operator=(AClass&& other) { return *this; }
    int member;
};


void main()
{
    AClass a;
    AClass& aRef = a;

    [=]() { return aRef.member; }; // "Attempting to reference a deleted function"

}

我假设它正在尝试调用已删除的复制构造函数,但我不明白为什么我试图通过 [=] 值的引用捕获,这意味着我不复制对象 AClass,我看不到任何副本是如何涉及的。我的理解是 lambda 看起来像这样:

struct lambda {
    AClass& refToAClass; // <--- this is the captured object
    lambda(AClass& captureVariable) : refToAClass(captureVariable) {}
    int operator()() const
    {
        return refToAClass.member;
    }
};


void main()
{
      // AND I CAN CONSTRUCT THE LAMBDA, NO COPYING OF AClass INVOLVED
      AClass a;
      AClass& aRef = a;

      lambda lam(aRef); // WORKS FINE
}

本案如何涉及副本?以及如何获取该引用?

【问题讨论】:

  • 如果你想通过引用捕获为什么不使用[&amp;]() 而不是[=]()
  • "按 [=] 值引用捕获" -> "按 [=] 值捕获引用" ?这是笔误吗?
  • 引用只是对象的另一个名称。您的 lambda 按值捕获所有内容,包括该对象,无论其名称如何。
  • @CoryKramer 因为在调用 lambda 函数时该特定引用将超出范围,所以我很确定。如果我通过引用来捕获引用,那么一旦函数退出,引用就会失效,对吧?
  • 这里解释一下是怎么回事:stackoverflow.com/questions/19676473/…因为不能复制所以不行。

标签: c++ lambda


【解决方案1】:

来自the standard

对于复制捕获的每个实体,在闭包类型中声明了一个未命名的非静态数据成员。这些成员的声明顺序是未指定的。 这种数据成员的类型如果实体是对对象的引用,则为被引用类型,如果实体是对函数的引用,则为对被引用函数类型的左值引用,或 否则为相应捕获实体的类型。不得抄袭匿名工会成员。

cpprefence 措辞更好:

每个数据成员的类型是对应的捕获实体的类型,除非实体具有引用类型(在这种情况下,对函数的引用被捕获为对被引用函数的左值引用,并且对对象的引用被捕获为引用对象的副本)。

【讨论】:

  • “并且对对象的引用被捕获为被引用对象的副本”,如何拥有一个 lambda 成员 AClass&aRef;复制引用的对象?就像传递给构造函数一样,如何涉及副本?
  • 引用被剥离。如果不是这样,捕获*this 之类的操作将不起作用,因为取消引用指针会产生左值引用。此外,我们会遇到 prvalues 的悬空引用问题,例如godbolt.org/z/eG6sWr
  • 将按值捕获视为使用auto:如果您使用auto v = aRef,则会遇到同样的问题。
【解决方案2】:

也许这有助于澄清我认为您对什么感到困惑:

template <typename T> void f(T arg) { }

int x = 10;
int& r = x;

auto y = r;     // y is int (copied value from reference)
f(r);           // T in f<T>() is deduced to int, arg in f() is int

在这两种情况下,如果您希望结果作为参考,您必须这样说:

template <typename T> void f(T& arg) { }

int x = 10;
int& r = x;

auto& y = r;     // y is reference-to-int
f(r);            // T in f<T>() is int, arg in f is ref-to-int

同样,在您的代码中:

AClass a;
AClass& aRef = a;

[=]() { return aRef.member; }; // "Attempting to reference a deleted function"

捕获的 aRef 是尝试从对不同对象的引用中复制局部变量。使用 [=] 表示按值复制,它确实将其读作“哦,源是引用,所以我将按值复制引用并将引用存储在参考。”它实际上是说,“如果源类型具有引用限定符,请忽略并考虑剩下的内容。您想按值复制?好的,我将 AClass 复制到一个新变量中。哦等等......不能。抱歉。”

你想要的是:

[&]() { return aRef.member; };

或者更具体地说:

[&aRef]() { return aRef.member; };

这告诉捕获的类型它应该是一个引用。在这种情况下,源类型不如目标类型重要。

【讨论】:

  • 如果 aRef 在调用函数时超出范围,则 lambda 内部的引用无效,对吧?
  • @Zebrafish lambda 中的引用是对实际对象的引用,而不是对 aRef 的引用。
  • 正确的悬空引用是进行按引用捕获时需要考虑的问题。如果 lambda 的寿命超过引用的值,那么如果使用它,就会有未定义的行为。
  • @ChrisUzdavinis 没关系,我认为通过引用捕获引用与通过引用捕获对象本身不同。显然情况并非如此。
猜你喜欢
  • 2013-04-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-04
  • 2019-10-28
  • 1970-01-01
  • 2011-07-15
相关资源
最近更新 更多