【问题标题】:C++11: In what order are lambda captures destructed?C++11:lambda 捕获以什么顺序被破坏?
【发布时间】:2012-09-20 20:41:48
【问题描述】:

假设我有两个本地智能指针,foobar

shared_ptr<Foo> foo = ...
shared_ptr<Bar> bar = ...

这些智能指针是资源的包装器,出于某种原因必须按照foo,然后bar 的顺序销毁。

现在我想创建一个使用 foobar 的 lambda,但比包含它们的作用域更长。所以我会按价值捕获它们,如下所示:

auto lambda = [foo, bar]() { ... };

这会在函数对象中创建foobar 的副本。当函数对象被破坏时,这些副本也将被破坏,但我关心这种情况发生的顺序。所以我的问题是:

当一个 lambda 对象被破坏时,它的按值捕获被破坏的顺序是什么?我如何(希望)影响这个顺序?

【问题讨论】:

  • 我认为考虑[=]会很有趣。
  • @R.MartinhoFernandes: [foo,bar] 等价于 [=foo,=bar],即它是一个副本。
  • @David :我认为他的字面意思是[=],即在不列出变量自己的情况下考虑声明顺序。 (显然,现在这是一个有争议的问题,因为无论捕获如何,声明顺序都未指定。)

标签: c++ lambda c++11 raii


【解决方案1】:

规范涵盖了这个……有点。从 5.1.2 第 14 段开始:

如果实体被隐式捕获并且捕获默认值为 =,或者如果使用不包含 & 的捕获显式捕获实体,则该实体被复制捕获。对于通过副本捕获的每个实体,在闭包类型中声明了一个未命名的非静态数据成员。 这些成员的声明顺序未指定。

添加了重点。因为未指定声明顺序,所以未指定构造顺序(因为构造顺序与声明顺序相同)。因此,销毁顺序是未指定的,因为销毁的顺序与构造的顺序相反。

简而言之,如果您需要关心声明顺序(以及与其相关的各种构造/销毁顺序),您不能使用 lambda。您需要制作自己的类型。

【讨论】:

  • 您不能在 lambda 末尾重置共享指针之一,以确保它释放其资源而不是在销毁期间?
  • 没关系;看我的回答。 lambda 必须是可变的才能工作。
  • @Nicol:感谢您的澄清。我对他们没有指定顺序感到有点困惑——通常,在 C++ 中,关于构造和销毁顺序的所有内容都是明确指定的。但至少他们指定它是未指定的,因此不会依赖特定编译器碰巧使用的顺序。
  • 多么不幸。我很惊讶这没有定义。他们是否只是没有时间决定推迟到某个未来版本的 C++ 再做决定?这可能会在 C++14 中解决吗?
【解决方案2】:

正如尼科尔所说,破坏的顺序是不确定的。

但是,您不必依赖 lambda 的破坏。您应该能够在 lambda 结束时简单地重置 foo,从而确保它在 bar 之前释放其资源。不过,您还必须将 lambda 标记为 mutable。这里唯一的缺点是你不能多次调用 lambda 并期望它工作。

auto lambda = [foo, bar]() mutable { ...; foo.reset(); };

如果您确实需要多次调用您的 lambda,那么您需要想出一些其他方法来控制释放的顺序。一种选择是使用具有已知数据成员顺序的中间结构,例如std::pair&lt;&gt;

auto p = std::make_pair(bar, foo);
auto lambda = [p]() { auto foo = p.second, bar = p.first; ... };

【讨论】:

  • 在我的情况下,lambda 确实保证只执行一次,因此您的手动重置方法应该可以正常工作。我忘记了,为了释放 shared_ptr 指向的内容,您不一定要破坏它。非常感谢!
【解决方案3】:

与其担心破坏的顺序是什么,您应该解决这是一个问题的事实。请注意,您对两个对象都使用了共享指针,您可以通过在对象中添加一个共享指针来确保销毁顺序,该共享指针需要使另一个对象寿命更长。到那时,foobar 是否被提前销毁都无关紧要。如果顺序正确,共享指针的销毁将立即释放对象。如果顺序不正确,则附加的共享指针将保持对象处于活动状态,直到另一个对象消失。

【讨论】:

    【解决方案4】:

    根据我拥有的 C++11 文档(即免费赠品,稍早于批准 n3242),第 5.1.2 节,第 21 节,捕获按声明顺​​序构建并以相反的声明顺序销毁。但是,未指定申报顺序(第 14 段)。所以答案是,“以未指定的顺序”和“你不能影响它”(我想,通过编写编译器除外)。

    如果 bar 真的需要在 foo 之前被销毁,那么 bar 保存一个指向 foo(或类似的东西)的共享指针是明智的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-09-09
      • 1970-01-01
      • 2016-12-28
      • 1970-01-01
      • 2016-09-04
      • 2016-08-27
      • 2012-12-15
      相关资源
      最近更新 更多