std :: future如何影响关联的std :: packaged_task的生命周期?

时间:2017-11-09 13:23:15

标签: c++ c++11 lambda object-lifetime std-future

我有一个<Button Command="{Binding DataContext.ConnectionSelectCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" FocusManager.FocusedElement="{Binding ElementName=InstanceName}" Style="{DynamicResource DashboardButton}" Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ItemsControl}}"> <TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" Text="{Binding Name}" /> <Button.ContextMenu> <ContextMenu> <MenuItem Header="Delete" Command="{Binding PlacementTarget.Tag.ConnectionRemoveCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" CommandParameter="{Binding}" /> </ContextMenu> </Button.ContextMenu> </Button> 包含一个lambda,它通过复制捕获变量。当删除此std::packaged_task时,我希望生成在lambda内的变量被破坏,但我注意到如果我为此std::packaged_task得到关联的std::future,{{1} } object扩展了lambda中变量的生命周期。

例如:

std::packaged_task

可能的输出是:

future

因此lambda中的对象的生命周期扩展到#include <iostream> #include <future> class Dummy { public: Dummy() {std::cout << this << ": default constructed;" << std::endl;} Dummy(const Dummy&) {std::cout << this << ": copy constructed;" << std::endl;} Dummy(Dummy&&) {std::cout << this << ": move constructed;" << std::endl;} ~Dummy() {std::cout << this << ": destructed;" << std::endl;} }; int main() { std::packaged_task<void()>* p_task; { Dummy ScopedDummy; p_task = new std::packaged_task<void()>([ScopedDummy](){std::cout << "lambda call with: " << &ScopedDummy << std::endl;}); std::cout << "p_task completed" << std::endl; } { std::future<void> future_result; { future_result = p_task->get_future(); (*p_task)(); delete p_task; } std::cout << "after p_task has been deleted, the scope of future_result determines the scope of the dummy inside p_task" << std::endl; } std::cout << "p_task cleans up when future_result dies" << std::endl; } 的范围。

如果我们注释掉0x7fff9cf873fe: default constructed; 0x7fff9cf873ff: copy constructed; 0x1904b38: move constructed; 0x7fff9cf873ff: destructed; 0x7fff9cf873fe: destructed; lambda call with: 0x1904b38 after p_task has been deleted, the scope of future_result determines the scope of the dummy inside p_task 0x1904b38: destructed; p_task cleans up when future_result dies 行,可能的输出是:

future_result

我一直想知道这里有什么机制,future_result = p_task->get_future();是否包含一些保持关联对象存活的链接?

1 个答案:

答案 0 :(得分:7)

查看gcc7.2.0 packaged_task sources,我们读到:

packaged_task(allocator_arg_t, const _Alloc &__a, _Fn &&__fn)
    : _M_state(__create_task_state<_Res(_ArgTypes...)>(std::forward<_Fn>(__fn), __a)){}

~packaged_task()
{
  if (static_cast<bool>(_M_state) && !_M_state.unique())
    _M_state->_M_break_promise(std::move(_M_state->_M_result));
}

其中_M_state是内部packaged_task共享状态的shared_ptr。因此,事实证明 gcc 将可调用数据存储为packaged_task 共享状态的一部分,因此将可调用生命周期绑定到packaged_task,future,shared_future最终死亡的生命周期。

相比之下, clang 没有,当打包的任务被销毁时会破坏可调用的对象(实际上,我的clang副本会将可调用的内容存储为正确的成员)。

谁是对的?标准对存储的任务生命周期不是很清楚;从一方面来看,我们有

  

<强> [[futures.task]]

     

packaged_task定义包装函数或可调用对象的类型,以便函数或可调用对象的返回值在将来调用时存储。

     

packaged_task(F&amp; f)[...]构造一个具有共享状态的新的packaged_task对象,并用std :: forward(f)初始化对象的存储任务

     

packaged_task(packaged_task&amp;&amp; rhs)[...] 将存储的任务从rhs移动到* this。

     

reset()[...]效果:好像* this = packaged_task(std :: move(f)),其中f是存储在* this 中的任务。

表明可调用对象由packaged_task拥有,但我们也有

  

<强> [[futures.state]]

     

- 本子条款中介绍的许多类使用某种状态来传达结果。 此共享状态由一些状态信息和一些(可能尚未评估)结果组成,这些结果可能是(可能是无效的)值或异常。 [注意:本节中定义的期货,承诺和任务引用此类共享状态。 -endnote]

     

- [注意:结果可以是任何类型的对象包括计算结果的函数,如async [...]]

  

<强> [futures.task.members]

     

-packaged_task(F&amp;&amp; f); [...]调用f的副本应该与调用f [...]的行为相同    - 〜packaged_task();效果:放弃任何共享状态

建议可调用可以存储在共享状态中,并且不应该依赖任何可调用的每个实例行为(这可以被解释为包括生命周期副作用的可调用结束;顺便说一下,这也意味着你的callable不是严格有效的,因为它的行为与它的副本不同);此外,没有提及dtor中存储的任务。

总而言之,我认为clang更加一致地遵循措辞,尽管似乎明确禁止gcc行为。也就是说,我同意这应该更好地记录,因为否则可能会导致令人惊讶的错误......