C++ lambda构造函数参数能够捕获已构造的变量吗?

13
以下代码是可以编译通过的。但是否存在任何悬空引用问题呢?
    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函数会更复杂。)


值得注意的是,如果您在构造函数中使用lambda表达式,可能会发生不良后果。 - Michael Anderson
1
如果您使用clang捕获变量并进行复制,则会收到警告“在其自身初始化中使用时变量未初始化”。 - Drax
2个回答

8

但是是否存在悬挂引用问题呢?

这完全取决于你对 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

1
那么说我的模式并不比一般的悬空引用更危险是合理的吗?在这种情况下,我的 foo 的生命周期不是问题。我发现这种构造方式相当奇怪,捕获一个仍在构造中的对象的引用。 - Andrew Lazarus
@AndrewLazarus 是的,基本上就是这样。有点奇怪。 - Barry

5

这个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对象的副本(该对象持有对foo的引用(即mFoo))。
这意味着在某些情况下可能会出现悬空引用问题,例如:
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.
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接