std::async 的非阻塞调用:这个版本有多危险?

non-blocking call of std::async: how is this version dangerous?

前段时间我在寻找一种无需存储 std::future 即可调用 std::async 的方法,因此不会在范围末尾阻塞执行。我发现 this answer 将捕获的 std::shared_ptr 用于 std::future,因此允许对 std::async.

进行非阻塞调用

另一种推迟析构函数调用的方法是完全阻止它被调用。这可以通过 operator new.

的就地构造来实现

考虑这个版本,它使用静态线程本地存储来构建就地 std::future<void>:

template <class F>
void call_async(F&& fun) {
    thread_local uint8_t buf[sizeof(std::future<void>)] = {0};
    auto fut = new(buf) std::future<void>();
    *fut = std::async(std::launch::async, [fun]() {
        fun();
    });
}

此版本不会产生任何与堆分配相关的开销,但它似乎非常非法,但我不确定具体原因。

我知道在构造对象之前就使用它是 UB,但事实并非如此。我不确定为什么在这种情况下不调用 delete 会在 UB 中解析(对于堆分配,它不是 UB)。

我看到的可能问题:

https://ideone.com/C44cfe

更新

直接在静态存储中构造一个对象(正如 IlCapitano 在评论中提到的那样)将在每次调用移动赋值时阻塞(共享状态将被销毁,阻塞已删除对它的最后引用的线程)。

不调用析构函数将导致泄漏,因为未释放对共享状态的引用。

在不调用析构函数的情况下结束非平凡对象的生命周期是未定义的行为,一旦有第二次 call_async 调用就会发生这种情况。

如果唯一的选择是未定义的行为,那么“与堆分配相关的开销”是用词不当。 async 返回的 future 必须存在于 某个地方

更新后的代码定义了行为:在启动下一个调用之前等待上一个调用完成。

调用 std::async 并忽略结果听起来像是“即发即忘”。最简单的方法是不使用 std::async,而是创建一个分离线程:

std::thread thr(func, data...);
thr.detach();