通过右值引用传递C++/WinRT协程参数引发悬垂引用问题?
C++/WinRT协程参数悬垂问题咨询
根据C++/WinRT官方文档中的示例:
IASyncAction DoWorkAsync(Param const& value) { auto safe_value = value; // 此处可以访问safe_value和value co_await DoOtherWorkAsync(); // 此处只能访问safe_value(不能访问value) }
这属于合理的悬垂引用问题。不过我之前发现必须采用如下写法:
IASyncAction DoWorkAsync(Param value) { auto safe_value = value; // 此处可以访问value co_await DoOtherWorkAsync(); // 此处只能访问safe_value(不能访问value) }
而最近我花费大量时间调试后,又遇到了类似问题:
IASyncAction DoWorkAsync(Param&& value) { co_await DoOtherWorkAsync(); // value的析构函数被调用 some_function(std::move(value)); // 崩溃 }
请问这种情况是否是预期行为?官方文档并未提及该问题,背后的原理是什么?
解答
这完全是预期行为,核心原因源于C++协程的参数生命周期绑定规则:
协程参数的生命周期本质
C++协程不会自动将参数捕获到协程的状态帧中——除非你显式把它们复制或移动到协程内部的持久化变量里。在第一个co_await挂起协程前,函数参数仍处于原函数的栈帧中;一旦协程挂起,原函数的栈帧会被销毁,所有栈上的参数(包括右值引用)都会随之析构。右值引用参数的特殊风险
Param&& value作为右值引用,本身是栈上的引用变量。当co_await DoOtherWorkAsync()挂起协程时,DoWorkAsync的原栈帧被销毁,value引用的对象(无论它原本是临时对象还是被std::move传递过来的对象)生命周期都会在此刻结束——因为你没有任何代码将它转移到协程状态帧中。等协程恢复执行时,value已经是悬垂引用,此时调用std::move(value)必然触发未定义行为(崩溃)。和前两种写法的对比
- 官方的
const&写法:提前将value复制到safe_value(协程内的局部变量会被捕获到状态帧),所以挂起后safe_value仍有效; - 值传递的写法:参数
value本身是栈上对象,挂起后同样会被销毁,因此仍需通过safe_value留存对象到协程状态帧; - 右值引用的写法:既未复制对象,也未将其移动到协程状态帧,挂起后原对象被析构,自然引发崩溃。
- 官方的
正确的修复写法
对于右值引用参数,需要在协程挂起前,将对象移动到协程内的持久化变量中:IASyncAction DoWorkAsync(Param&& value) { auto safe_value = std::move(value); // 将右值对象转移到协程状态帧的变量中 co_await DoOtherWorkAsync(); some_function(std::move(safe_value)); // 此时safe_value有效 }
官方文档未提及右值引用的情况,是因为这属于标准C协程的通用规则,而非C/WinRT特有的行为——C++/WinRT的协程实现基于标准C++协程,因此必须遵循标准的生命周期约束。
内容的提问来源于stack exchange,提问作者Tom Huntington
相关产品推荐
相关产品推荐

