如何解决C++ 协程:从最终挂起点调用 `handle.destroy` 是否有效?
在 C++ 协程的最终暂停中调用 handle.destroy()
是否有效?
根据我的理解,这应该没问题,因为协程当前已暂停并且不会再次恢复。
仍然,AddressSanitizer 报告以下代码片段的 heap-use-after-free
:
#include <experimental/coroutine>
#include <iostream>
using namespace std;
struct final_awaitable {
bool await_ready() noexcept { return false; }
void await_resume() noexcept {}
template<typename PROMISE> std::experimental::coroutine_handle<> await_suspend(std::experimental::coroutine_handle<PROMISE> coro) noexcept {
coro.destroy(); // Is this valid?
return std::experimental::noop_coroutine();
}
};
struct task {
struct promise_type;
using coro_handle = std::experimental::coroutine_handle<promise_type>;
struct promise_type {
task get_return_object() { return {}; }
auto initial_suspend() { return std::experimental::suspend_never(); }
auto final_suspend() noexcept { return final_awaitable(); }
void unhandled_exception() { std::terminate(); }
void return_void() {}
};
};
task foo() {
cerr << "foo\n";
co_return;
}
int main() {
auto x = foo();
}
使用 clang 11.0.1 和编译标志 -stdlib=libc++ --std=c++17 -fcoroutines-ts -fno-exceptions -fsanitize=address
编译时。 (见https://godbolt.org/z/eq6eoc)
(我实际代码的简化版本。你可以在https://godbolt.org/z/8Yadv1中找到完整的代码)
这是我的代码中的问题还是 AddressSanitizer 中的错误阳性?
解决方法
如果您 100% 确定之后没有人会使用协程承诺,则这是完全有效的。调用 coroutine_handle::destroy
相当于调用协程 promise 析构函数。
既然如此,那当初为什么要这样做呢?只需从 std::suspend_never
final_suspend
std::suspend_never final_suspend() const noexcept { return {}; }
它相当于你的代码。如果我们想在协程完成后对协程承诺做一些有意义的事情,比如返回协程的存储结果,我们希望在 final_suspend
中挂起协程。由于您的 task
对象不存储或返回任何内容,我不明白为什么要最终挂起它。
请注意,如果您使用第三方库,例如我的 concurrencpp,您需要确保可以销毁不属于您的承诺。协程承诺可能会被挂起,但仍被其 coroutine_handle
引用到其他地方。这又回到了第 1 点。就我的库而言,它不安全,因为可能是 result
对象仍然引用它。
总而言之,在以下情况下可以调用 coroutine_promise::destroy
:
- 协程已暂停(当您到达
final_suspend
时) - 没有人会在销毁后使用该协程承诺(确保没有类似未来的对象引用该协程!)
-
destroy
之前没有被调用过(双重删除)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。