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

Fla*_*gas 7 c++ lambda object-lifetime c++11 std-future

我有一个std::packaged_task包含lambda的lambda,它通过复制捕获变量.当这个std::packaged_task被删除,我希望在lambda里面的变量生活是破坏,但我注意到,如果我得到了相关std::future为此std::packaged_task,该future对象扩展拉姆达内部变量的生存期.

例如:

#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;
}
Run Code Online (Sandbox Code Playgroud)

可能的输出是:

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
Run Code Online (Sandbox Code Playgroud)

所以lambda中的对象的生命周期延长了future_result.

如果我们注释掉该行future_result = p_task->get_future();,可能的输出是:

0x7fff57087896: default constructed;
0x7fff57087897: copy constructed;
0x197cb38: move constructed;
0x7fff57087897: destructed;
0x7fff57087896: destructed;
lambda call with: 0x197cb38
0x197cb38: destructed;
after p_task has been deleted, the scope of future_result determines the scope of the dummy inside p_task
p_task cleans up when future_result dies
Run Code Online (Sandbox Code Playgroud)

我一直想知道在这里发挥什么机制,是否std::future包含一些保持关联对象存活的链接?

Mas*_*nes 7

看一下gcc7.2.0的packaged_task来源,我们读到:

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));
}
Run Code Online (Sandbox Code Playgroud)

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

相比之下,clang没有,当打包的任务被破坏时会破坏callable(实际上,我的clang副本会将callable存储为正确的成员).

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

[[futures.task]]

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

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

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

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

这表明可调用的是由packaged_task拥有的,但我们也有

[[futures.state]]

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

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

[futures.task.members]

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

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

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