std::future如何影响相关std::packaged_task的生存期
How does std::future affects the lifetime of an associated std::packaged_task?
我有一个std::packaged_task
,它包含一个通过复制捕获变量的lambda。当这个std::packaged_task
被删除时,我希望lambda中的变量被销毁,但我注意到,如果我得到这个std::packaged_task
的关联std::future
,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
因此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
我一直想知道什么机制在这里发挥作用,std::future
是否包含一些链接来保持相关对象的活动?
查看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));
}
其中CCD_ 9是内部packaged_task共享状态的shared_。因此,gcc将可调用存储为packaged_task共享状态的一部分,从而绑定了packaged_task、future、shared_future中最后一个死亡的可调用生存期。
相比之下,clang没有,当打包的任务被销毁时,会销毁可调用的(事实上,clang的副本会将可调用的存储为适当的成员)。
谁是对的?该标准对存储的任务寿命不是很清楚;从一方面来说,我们有
[[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的行为相同[…]-~packaged_task();效果:放弃任何共享状态
建议可调用文件可以存储在共享状态,并且不应依赖于任何可调用的每实例行为(这可能被解释为包括可调用的生命周期结束副作用;顺便说一句,这也意味着您的可调用文件不是严格有效的,因为它的行为与其副本不同);此外,没有提及dtor中存储的任务。
总的来说,我认为clang更一致地遵循了措辞,尽管似乎没有任何明确禁止gcc行为。也就是说,我同意应该更好地记录这一点,因为否则可能会导致令人惊讶的错误。。。
- 使用std::multimap迭代器创建std::list
- C++中std::resize(n)和std::shrink_to_fit之间的区别
- 来自 std::list 的迭代器 .end() 按预期返回"0xcdcdcdcdcdcdcdcd"但 .begin()
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 如何导出包含具有"std::unique_ptr"值的"std::map"属性的
- 从持续时间构造std::chrono::system_clock::time_point
- std::具有相同基类的类的变体
- std::向量与传递值的动态数组
- 使用std::vector的OpenCL矩阵乘法
- std::map<struct,struct>::find 找不到匹配项,但是如果我循环通过 begin() 到 end(),我在那里看到匹配项
- std::condition_variable::wait()如何评估给定的谓词
- 如何获取std::result_of函数的返回类型
- std::原子加载和存储都需要吗
- 将对象移动到std::shared_ptr
- POCO::PostgreSQL:如何将std::vector支持添加到`Binder::bind`
- 使用一个考虑到std::map中键值的滚动或换行的键
- 如何从 std::atomic 中提取指针 T<T>?
- 为什么 std::unique 不调用 std::sort?
- 使用std::函数映射对象方法
- 可组合的lambda/std::函数与std::可选