C++协程默认引用参数的生命周期相关问题咨询
C++协程默认参数生命周期问题原理详解
核心规则前提
C++标准对协程形参的生命周期有明确规定:
协程形参的生命周期与调用方的协程调用表达式绑定,而非与协程本身的执行周期绑定。
你之前了解到的「参数的生命周期属于调用方作用域的一部分」完全正确,默认参数的异常表现正是这条规则的直接结果,且该规则不受initial_suspend返回值影响,即使协程一开始就挂起,调用表达式结束后临时参数依然会被销毁。
测试代码执行流程拆解
你的运行结果完全符合标准定义的行为,我们逐步骤梳理执行过程:
- 调用点参数构造:执行
coro_test<Test>()时,默认参数生成临时Test对象,绑定到右值引用形参t。 - 协程首次执行挂起:由于你的
promise_type::initial_suspend返回std::suspend_never,协程立刻开始执行,直到第一个co_yield i(此时i=1)触发挂起,返回generator对象赋值给gen。 - 调用表达式结束,默认参数销毁:
coro_test<Test>()的整个调用表达式此时已经执行完毕,所有绑定到该调用表达式的临时对象(包括默认参数构造的Test对象)都会被销毁,因此你会先看到析构日志,再看到后续的输出。 - 后续协程恢复触发UB:之后每次
++gen恢复协程时,形参t引用的是已经销毁的对象,属于未定义行为,自然不会触发i == t.i的判断输出100。
和普通函数的差异原因
普通函数的调用表达式会等待函数体完全执行完毕才结束,因此默认参数构造的临时对象会伴随整个函数执行周期,不会提前销毁。但协程会在执行过程中挂起返回调用方,调用表达式会在协程第一次挂起(或者首次执行到末尾)时结束,关联的临时对象就会被立刻清理。
补充注意点
该问题本质和默认参数无关,只要协程形参是引用类型(左值/右值引用),引用的对象在协程执行完成前被销毁,就会触发同样的问题:
- 如果你给协程传递的是调用方栈上的局部变量,后续协程恢复时该变量已经出作用域销毁,同样会出现野引用。
- 如果协程形参是值类型,参数会被拷贝/移动到协程帧中,生命周期和协程帧完全绑定,就不会出现提前销毁的问题。
解决方案
- 方案1:将需要在挂起后访问的参数,显式移动/拷贝到协程局部变量中(协程局部变量存储在协程帧,生命周期和协程一致):
template<class T> generator<int> coro_test(T&& t = T()) { T local_t = std::forward<T>(t); // 存入协程帧 int i = 0; while(i++ < 3) co_yield i; if(i == local_t.i) co_yield 100; }
- 方案2:对于不需要完美转发的场景,直接将形参定义为值类型,让参数直接保存在协程帧中。
内容的提问来源于stack exchange,提问作者igel
相关产品推荐
相关产品推荐

