C++ 11 lambdas:成员变量捕获陷阱

Ale*_*x B 40 c++ c++11

考虑以下代码:

#include <memory>
#include <iostream>

class A
{
public:
    A(int data) : data_(data)
    { std::cout << "A(" << data_ << ")" << std::endl; }
    ~A() { std::cout << "~A()" << std::endl; }
    void a() { std::cout << data_ << std::endl; }
private:
    int data_;
};

class B
{
public:
    B(): a_(new A(13)) { std::cout << "B()" << std::endl; }
    ~B() { std::cout << "~B()" << std::endl; }
    std::function<void()> getf()
    {
        return [=]() { a_->a(); };
    }
private:
    std::shared_ptr<A> a_;
};

int main()
{
    std::function<void()> f;
    {
        B b;
        f = b.getf();
    }
    f();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这看起来我正在a_按值捕获共享指针,但是当我在Linux(GCC 4.6.1)上运行它时,会打印出来:

A(13)
B()
~B()
~A()
0
Run Code Online (Sandbox Code Playgroud)

显然,0是错误的,因为A已经被破坏了.它看起来像是this被捕获并且用于查找this->a_.当我从改变捕获列表我怀疑被证实[=][=,a_].然后打印正确的输出,并且对象的生命周期符合预期:

A(13)
B()
~B()
13
~A()
Run Code Online (Sandbox Code Playgroud)

问题:

此行为是由标准,实现定义还是未定义指定的?或者我疯了,这是完全不同的东西?

Nic*_*las 40

此行为是否由标准指定

是.捕获成员变量总是通过捕获来完成this; 它是访问成员变量的唯一方法.在成员函数的范围内a_相当于(*this).a_.Lambdas也是如此.

因此,如果您使用this(隐式或显式),那么您必须确保在lambda实例存在时对象保持活动状态.

如果要按值捕获它,则必须明确这样做:

std::function<void()> getf()
{
    auto varA = a_;
    return [=]() { varA->a(); };
}
Run Code Online (Sandbox Code Playgroud)

如果您需要规格报价:

lambda-expression的复合语句产生函数调用操作符的函数体(8.4),但是为了查找名称(3.4),确定它的类型和值(9.3.2)并转换id-expressions引用使用(*this)(9.3.1)将非静态类成员转换为类成员访问表达式,在lambda表达式的上下文中考虑复合语句.

  • @Nicol:对我来说,问题不在于令人讨厌的旧C++让我自己开枪,问题在于lambda的"按价值捕获"默默地捕获了"this"而不是"_a".这是值得一个可选的警告,IMO,是否返回lambda.如果警告鼓励你明确写出`(*this)._ a`,那么很明显它是被捕获的'this`.编译器警告其他不那么危险且不太巧妙的事情. (12认同)
  • @Nicol:我不想"惩罚"任何人,我认为一个可选的警告是合理的.你没有必要启用它,所以我发现你非常热情,甚至连其他人都无法使用它.很明显,`[=]`捕获`this`,而不是`_a`,因为你完全熟悉该领域的标准.这很好,但编译器警告语言的难度微妙. (10认同)
  • 恕我直言,这是最可怕的lambdas之一.我希望所有编译器最终都会为此添加一个很大的警告. (8认同)
  • @Dani但是将`shared_ptr`返回给成员并不是错误.这似乎是一个潜在的常见错误,因为我在转换现有代码时遇到了它.我确实认为有一个警告(至少在'-Wextra`上),并且当您通过`this->`显式访问该成员时可能会被静音,或者将其添加到捕获列表中. (6认同)
  • @Martin:你为什么要这样警告?如果lambda在算法内部使用,那将完全没问题.他只是遇到了一个问题,因为他向某人回复了这个问题. (4认同)
  • @Nicol:在函数调用内部使用这个lambda是"永远安全"是错误的.在内部或外部使用它,它仍然捕获`this`(它在lambda的源中无处可见),而不是`_a`.你可以重置或以其他方式修改智能指针而不留下范围,如果你的期望是`_a`被值捕获,那么你的代码仍然是错误的.可选警告的重点是识别一个相当合理的程序员期望不正确的地方,虽然不正确但期望`[=]`捕获`_a`并非完全不合理. (4认同)
  • @Nicol - 我想要一个警告,当一个成员不能通过lambda中的`this->`显式访问时,因为完全不明显的是,按值捕获所有内容的lambda只捕获`this `*指针*按值,而不是成员,虽然你可以在lambda中引用没有this指针的成员.(我知道从实现POV开始是有意义的,如果lambda仅在成员函数中使用,也是有意义的,但是一旦lambda传递,你就会等待崩溃!) (3认同)