C++11与C++17下-fno-elide-constructors对NRVO的行为差异疑问
解答:C++17下
-fno-elide-constructors的行为差异原因 你的观察完全正确,这不是强制NRVO的效果,而是C++17对对象初始化语义的核心改动——和你理解的NRVO场景无关,且不受-fno-elide-constructors选项的限制。
核心逻辑拆解
C++11的执行流程(带-fno-elide-constructors)
当执行rvo B {f1(A)};时,C++11的规则是:
f1内部用A拷贝构造y(对应Copy Constructor输出)- 因为关闭了NRVO,
f1返回时必须把y移动到一个临时对象里(第一次Move Constructor),然后销毁y(第一个Destructor) - 接着用这个临时对象移动构造
B(第二次Move Constructor),再销毁临时对象(第二个Destructor) - 最后销毁
B和A,总共四次析构,和你看到的输出一致。
C++17的执行流程(带-fno-elide-constructors)
C++17彻底改了这个场景的规则:当你用函数调用的结果直接初始化对象时,函数的返回值会直接构造到目标对象(也就是B)的内存里,全程不会产生中间临时对象。
f1内部同样用A拷贝构造y- 因为关闭了NRVO,
f1返回时还是要把y移动到B的内存(这就是你看到的那一次Move Constructor),然后销毁y(第一个Destructor) - 没有临时对象,所以不需要第二次移动构造,也少了一次临时对象的析构
- 最后销毁
B和A,总共三次析构,对应你看到的输出。
你理解的误区:这不是NRVO的强制行为
你以为这是NRVO场景,但其实:
- NRVO是指消除“函数内命名对象→返回临时对象”的拷贝/移动,
-fno-elide-constructors确实禁用了NRVO,所以f1返回y时的移动构造还是会发生(这也是为什么你能看到一次Move Constructor)。 - 而C17省略的是“临时对象→
B”的移动构造,这部分不属于拷贝消除的范畴——因为标准规定这种场景下根本就不会创建临时对象,所以-fno-elide-constructors对它完全无效。这是C17强制拷贝消除的另一类场景:当表达式是纯右值(比如函数返回值)且直接用来初始化对象时,直接把对象构造到目标内存,跳过所有中间步骤。
验证:拆初始化步骤就能回到C++11的行为
如果把main里的初始化拆成两步,比如:
int main() { rvo A{}; rvo temp = f1(A); // 先创建中间变量 rvo B {temp}; return 0; }
这时用C17加-fno-elide-constructors编译,输出就会和C11一致——因为显式创建了中间变量temp,语义回到了C++11的逻辑,会触发两次移动构造。
内容的提问来源于stack exchange,提问作者TCD
相关产品推荐
相关产品推荐

