prvalue语义下对象生命周期的困惑及链式场景推理依据咨询
示例代码
struct A { int *num; A(): num{new int(1)} {} A(A &&other) { num = std::move(other.num); std::cout << "moved"; } ~A() { *num = 2; } }; int* f(A a) { return a.num; } A returnA() { return A{}; } int returnInt() { return *f(returnA()); } // 疑问点
问题描述
我原本的理解是,prvalue语义会让returnA()中构造的对象直接作为f的实参初始化,不产生临时对象,这个对象会在f函数结束时销毁,所以returnInt里解引用的是已经被析构函数修改过的指针(值应该是2),但实际运行时returnInt返回1,说明解引用时A的析构函数还没调用,仿佛仍有临时对象在作用域内,这是为什么?
补充说明:从语义上看,returnInt的return语句中returnA()会创建临时对象,这和把returnA()的结果赋值给变量的情况(语义上无临时对象)不同。本例是两次prvalue语义的链式调用,由于prvalue语义可能违反as-if拷贝/移动规则,有没有可用于这类场景推理的理论依据?
解答
核心原因:临时对象的生命周期延长规则
C++标准明确规定,当prvalue作为函数实参传递时,它所绑定的临时对象的生命周期会延长到完整表达式结束,而非函数调用结束。
在你的代码中,return *f(returnA());的完整表达式是整个return语句。returnA()产生的prvalue临时对象,即便被用来初始化f的形参a,其生命周期也会延续到*f(...)的求值完成之后——也就是整个return语句执行完毕才会销毁。
关于prvalue“直接初始化”与临时对象的澄清
你提到的“prvalue语义直接作为实参初始化,不涉及临时对象”,本质是返回值优化(RVO)/复制消除的表现,但这仅适用于“将prvalue直接初始化具名变量”的场景(比如A a = returnA();)。而在函数实参传递场景中,prvalue会先绑定到一个临时对象,再用该临时对象初始化函数形参(即便移动构造被省略,临时对象的生命周期规则依然生效)。
链式调用中的生命周期规则
对于链式prvalue调用,C++标准的规则清晰:
- 当prvalue用于初始化对象(如函数形参)时,对应临时对象的生命周期会被延长到包含该初始化的完整表达式末尾。
- 这里的“完整表达式”指包含函数调用、解引用等操作的整个语句,而非单个函数的调用范围。
因此,你的代码中returnA()产生的临时对象(即便复制消除发生,本质与f的形参a是同一个对象),会在*f(returnA())求值完成后才触发析构函数。这就解释了返回值为1的原因——析构函数里的*num = 2是在解引用之后执行的。
关于as-if规则的说明
as-if规则允许编译器优化,但前提是不能改变程序的可观察行为。临时对象生命周期的延长是标准规定的语义,编译器不能违反该规则进行优化,否则会改变程序输出结果。所以即便prvalue的复制被消除,临时对象的生命周期依然要遵循标准的延长规则。
内容的提问来源于stack exchange,提问作者Elucidase

