You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的规则是:

  1. f1内部用A拷贝构造y(对应Copy Constructor输出)
  2. 因为关闭了NRVO,f1返回时必须把y移动到一个临时对象里(第一次Move Constructor),然后销毁y(第一个Destructor)
  3. 接着用这个临时对象移动构造B(第二次Move Constructor),再销毁临时对象(第二个Destructor)
  4. 最后销毁B和A,总共四次析构,和你看到的输出一致。

C++17的执行流程(带-fno-elide-constructors)

C++17彻底改了这个场景的规则:当你用函数调用的结果直接初始化对象时,函数的返回值会直接构造到目标对象(也就是B)的内存里,全程不会产生中间临时对象。

  1. f1内部同样用A拷贝构造y
  2. 因为关闭了NRVO,f1返回时还是要把y移动到B的内存(这就是你看到的那一次Move Constructor),然后销毁y(第一个Destructor)
  3. 没有临时对象,所以不需要第二次移动构造,也少了一次临时对象的析构
  4. 最后销毁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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 21:48:22