为啥我不能在 lambda 中捕获这个引用('&this')?

Posted

技术标签:

【中文标题】为啥我不能在 lambda 中捕获这个引用(\'&this\')?【英文标题】:Why can't I capture this by-reference ('&this') in lambda?为什么我不能在 lambda 中捕获这个引用('&this')? 【发布时间】:2013-04-25 17:44:31 【问题描述】:

我了解在 lambda 中捕获this(修改对象属性)的正确方法如下:

auto f = [this] ()  /* ... */ ;

但我很好奇我见过的以下特点:

class C 
    public:
        void foo() 
            // auto f = [] ()  // this not captured
            auto f = [&] ()  // why does this work?
            // auto f = [&this] ()  // Expected ',' before 'this'
            // auto f = [this] ()  // works as expected
                x = 5;
            ;
            f();
        

    private:
        int x;
;

让我感到困惑(并希望得到解答)的奇怪之处是以下工作的原因:

auto f = [&] ()  /* ... */ ; // capture everything by reference

以及为什么我不能通过引用显式捕获this

auto f = [&this] ()  /* ... */ ; // a compiler error as seen above.

【问题讨论】:

你为什么想要?就可以想象到对指针的引用可能有用的事物而言:this 无法更改,它不够大,无法更快地进行引用......并且无论如何它实际上并不存在,因此它没有真正的生命周期,这意味着对它的任何引用都会根据定义悬空。 this 是纯右值,而不是左值。 【参考方案1】:

[&this] 不起作用的原因是因为它是语法错误。 lambda-introducer 中每个逗号分隔的参数都是一个capture

capture:
    identifier
    & identifier
    this

您可以看到&this 在语法上是不允许的。不允许它的原因是因为您永远不想通过引用捕获this,因为它是一个小的 const 指针。您只想按值传递它 - 因此该语言不支持通过引用捕获 this

要明确捕获this,您可以使用[this] 作为lambda-introducer

第一个capture 可以是capture-default,即:

capture-default:
    &
    =

这意味着自动捕获我使用的任何内容,分别通过引用 (&) 或按值 (=) - 但是对 this 的处理是特殊的 - 在这两种情况下,由于给定的原因,它都是按值捕获的以前(即使默认捕获 &,这通常意味着通过引用捕获)。

5.1.2.7/8:

出于名称查找 (3.4) 的目的,确定 this (9.3.2) 的类型和值并转换 id- 使用(*this) (9.3.1) 将非静态类成员的表达式引用到类成员访问表达式中, 在 lambda 表达式的上下文中考虑复合语句 [OF THE LAMBDA]。

因此,当使用成员名称时,lambda 就好像它是封闭成员函数的一部分(例如在您的示例中使用名称 x),因此它将生成 this 的“隐式用法”,就像一个成员函数。

如果一个 lambda-capture 包含一个默认的 capture-default &,则 lambda-capture 中的标识符不应是 前面是&。如果 lambda 捕获包含 = 的默认捕获,则 lambda 捕获不应包含 this 及其包含的每个标识符都应以& 开头。标识符或this 不得出现超过 一次在 lambda 捕获中。

因此您可以使用[this][&][=][&,this] 作为lambda-introducer 按值捕获this 指针。

但是 [&this][=, this] 格式不正确。在最后一种情况下,gcc 宽恕地警告 [=,this] explicit by-copy capture of ‘this’ redundant with by-copy capture default 而不是错误。

【讨论】:

@KonradRudolph:如果你想通过价值捕捉一些东西而通过引用捕捉另一些东西怎么办?或者想对你捕捉到的内容非常明确? @KonradRudolph:这是一项安全功能。您可能会意外捕获您不打算捕获的名称。 @KonradRudolph:块级构造不会神奇地将指向它们使用的对象的指针复制到新的不可见匿名类型中,然后可以在封闭范围内生存 - 只需在表达式中使用对象名称. Lambda 捕获更危险。 @KonradRudolph 我会说“使用[&] 如果你正在做一些事情,比如创建一个要传递到控制结构的块”,但如果你正在生成一个将要生成的 lambda,则明确捕获用于不太简单的目的。 [&] 是一个可怕的想法,如果 lambda 将超过当前范围。然而,lambdas 的许多用途只是将块传递给控制结构的方法,并且块不会比它在范围内创建的块寿命更长。 @Ruslan:不,this 是关键字,this 不是标识符。【参考方案2】:

因为标准在捕获列表中没有&this

N4713 8.4.5.2 捕获:

lambda-capture:
    capture-default
    capture-list
    capture-default, capture-list

capture-default:
    &
    =
capture-list:
    capture...opt
    capture-list, capture...opt
capture:
    simple-capture
    init-capture
simple-capture:
    identifier
    &identifier
    this
    * this
init-capture:
    identifier initializer
    &identifier initializer

    出于 lambda 捕获的目的,表达式可能会引用本地实体,如下所示:

    7.3 this 表达式可能引用 *this。

因此,标准保证this*this 有效,而&this 无效。另外,捕获this 意味着捕获*this(这是一个左值,对象本身)通过引用而不是捕获this指针按值

【讨论】:

*this 通过value捕获对象

以上是关于为啥我不能在 lambda 中捕获这个引用('&this')?的主要内容,如果未能解决你的问题,请参考以下文章

在 C++11 中按值或引用使用 lambda 默认捕获的缺点?

为啥这个 ObjC 块在释放时不释放其捕获的引用?包括失败的单元测试

在 lambda 中,引用的按值捕获是不是会复制底层对象?

在 Java Lambda 中,为啥在捕获的变量上调用 getClass()

为啥在使用带有量词的字符类时在反向引用中捕获最右边的字符?

如何告诉 lambda 函数捕获副本而不是 C# 中的引用?