为何std::coroutine_handle采用裸指针而非std::unique_ptr管理协程?
你问到的点切中了C++协程设计的几个核心原则,下面直接拆解原因和用std::unique_ptr管理的弊端:
可拷贝性是核心需求
std::coroutine_handle的核心使用场景之一就是被拷贝传递:比如多个异步任务等待同一个协程完成、调度器转发协程句柄、或者把句柄存储到多个等待队列里。如果用std::unique_ptr来管理,句柄就只能移动不能拷贝,这会直接废掉绝大多数异步协作的场景——你没法让多个地方同时持有同一个协程的引用,而这是异步编程里的常见需求。职责分离的设计原则
C++标准库坚持单一职责:std::coroutine_handle只负责提供协程的操作接口(resume()、destroy()、done()等),而协程状态的生命周期管理完全交给返回类型(比如task、generator这类自定义类型)。这种设计允许开发者灵活适配不同的生命周期策略:比如用std::shared_ptr实现共享所有权、用内存池分配协程状态、甚至手动管理销毁时机。如果让handle自带unique_ptr,就把生命周期固定成了独占式,完全堵死了其他管理方式的可能性。避免语义混淆和错误
std::unique_ptr的所有权语义会给使用者传递错误信号:拿到handle就等于拥有了协程状态,这会导致意外的销毁行为——比如多个持有unique_ptr式handle的对象,第一个销毁的会直接释放协程状态,剩下的handle全部变成悬空指针,触发未定义行为。而裸指针式的handle明确告诉你:我只是协程状态的一个"引用",不负责销毁,你得自己通过返回类型管好生命周期。
另外还有一个隐性问题:协程状态不一定是堆分配的(虽然C++20标准协程默认是堆分配,但未来可能支持栈分配协程),这时候unique_ptr根本无法管理栈上的对象,强行使用会直接触发未定义行为。
- 兼容手动管理的高级场景
在一些性能敏感或自定义资源管理的场景中,开发者可能希望手动控制协程状态的销毁时机——比如把协程状态放到内存池里复用,或者和其他资源绑定在一起统一释放。如果handle自带unique_ptr,销毁时机就被固定在handle析构时,开发者完全没法干预这种行为,灵活性大打折扣。
内容的提问来源于stack exchange,提问作者Enlico

