问题 C ++ lambda构造函数参数可以捕获构造的变量吗?


下列 编译。但是,有没有任何悬挂参考问题?

    class Foo {
         Foo(std::function<void(int)> fn) { /* etc */ }
    }

    void f(int i, Foo& foo) { /* stuff with i and foo */ }

    Foo foo([&foo](int i){f(i, foo);});

似乎工作。 (真正的lambda当然更复杂。)


5324
2018-04-20 02:59


起源

可能值得注意的是,如果你在构造函数中使用lambda,可能会发生坏事。 - Michael Anderson
如果你通过副本捕获它,你会得到一个clang的警告 variable is uninitialized when used within its own initialization - Drax


答案:


但是,有没有任何悬挂参考问题?

这完全取决于你正在做什么 Foo。这是一个例子  有悬挂的参考问题:

struct Foo {
     Foo() = default;
     Foo(std::function<void(int)> fn) : fn(fn) { }
     std::function<void(int)> fn;
}

Foo outer;
{
    Foo inner([&inner](int i){f(i, inner);});
    outer = inner;
}
outer.fn(42); // still has reference to inner, which has now been destroyed

5
2018-04-20 03:08



那么说我的模式一般比悬挂引用更危险是合理的吗?我的一生 foo 在这种情况下不是问题。我发现这个结构很奇怪,捕获了一个仍在构建的对象的引用。 - Andrew Lazarus
@AndrewLazarus是的,基本上。太奇怪了。 - Barry


lambda表达式 [&foo](int i){f(i, foo);} 将导致编译器生成类似这样的闭包类(但不完全正确):

class _lambda
{
    Foo& mFoo; // foo is captured by reference

public:
    _lambda(Foo& foo) : mFoo(foo) {}

    void operator()(int i) const
    {
       f(i, mFoo);
    }
};

因此,申报 Foo foo([&foo](int i){f(i, foo);}); 被视为 Foo foo(_lambda(foo));。捕获 foo 本身在构造时在这种情况下没有问题,因为这里只需要它的地址(引用通常通过指针实现)。

方式 std::function<void(int)> 将在内部复制构造此lambda类型,这意味着Foo的构造函数参数 fn 持有一份副本 _lambda 对象(保存一个引用(即mFoo)) foo)。

这些暗示在某些情况下可能会出现悬挂参考问题,例如:

std::vector<std::function<void(int)>> vfn; // assume vfn live longer than foo

class Foo {
     Foo(std::function<void(int)> fn) { vfn.push_back(fn); }
}

void f(int i, Foo& foo) { /* stuff with i and foo */ }

Foo foo([&foo](int i){f(i, foo);});

....

void ff()
{
    // assume foo is destroyed already,
    vfn.pop_back()(0); // then this passes a dangling reference to f.
}

4
2018-04-20 12:07