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

返回值优化的原理是什么?对象创建未触发构造/赋值的疑问

嘿,这个问题问到点子上了!你碰到的是C++里**返回值优化(RVO)**的经典场景,咱们把这个事儿掰碎了说:

首先先补全你提到的可复现代码(方便大家理解):

#include <iostream>
using std::cout;
using std::endl;

class D {
public:
    D() { cout << "✅ 默认构造函数" << endl; }
    D(const D&) { cout << "🔄 拷贝构造函数" << endl; }
    D(D&&) { cout << "🚀 移动构造函数" << endl; }
    D& operator=(const D&) { cout << "🔄 拷贝赋值运算符" << endl; return *this; }
    D& operator=(D&&) { cout << "🚀 移动赋值运算符" << endl; return *this; }
    ~D() { cout << "❌ 析构函数" << endl; }
};

D foo() {
    return D(); // 返回匿名临时对象
}

int main() {
    cout << "开始创建d4:" << endl;
    D d4(foo());
    cout << "d4创建完成" << endl;
    return 0;
}
为什么D d4(foo());没调用任何构造/赋值函数?

答案就是编译器直接跳过了中间临时对象的创建,把foo()里要返回的对象直接构造在d4的内存空间里了!

按照代码的字面逻辑,本该走这三步:

  1. foo()里创建一个临时D对象(调用默认构造)
  2. 把这个临时对象移动/拷贝给d4(调用移动/拷贝构造)
  3. 销毁foo()里的临时对象(调用析构)

但RVO这个优化直接把步骤1和2合并了——编译器告诉foo():“别在你自己的栈帧里造对象了,直接去main()里d4的位置造!” 这样一来,中间的临时对象压根不存在,自然不需要调用任何拷贝/移动构造,也碰不到赋值运算符(赋值是给已存在的对象赋值,这里是直接构造新对象)。

返回值优化的核心原理和规则

RVO是C标准明确允许的特殊优化——哪怕它会改变程序的可观测行为(比如跳过了构造函数的打印),这也是C里少数几个允许“违背”代码字面逻辑的优化手段。它主要分两种情况:

  • 纯RVO:当函数返回匿名临时对象时(比如return D();),编译器几乎都会直接把对象构造在调用方的目标内存里。
  • 命名返回值优化(NRVO):如果函数返回一个命名的局部变量(比如D temp; return temp;),只要这个变量是函数内的纯局部对象,没有被复杂逻辑修改,编译器也能直接把它构造在调用方的目标位置。

几个关键注意点:

  1. RVO是编译器的可选优化,但主流编译器(GCC、Clang、MSVC)在默认或开启优化(比如-O2)时都会自动执行。
  2. 如果你想强制禁用RVO观察原本的行为,可以用编译器选项:GCC/Clang加-fno-elide-constructors,MSVC可以用/Od关闭所有优化,或者部分版本支持-fno-elide-constructors。
  3. 赋值场景(比如D d4; d4 = foo();)才会触发赋值运算符,但有些编译器也会优化这一步,把它变成直接构造。
额外验证:如果禁用RVO会发生什么?

当你用-fno-elide-constructors编译上面的代码,输出会变成:

开始创建d4:
✅ 默认构造函数
🚀 移动构造函数
❌ 析构函数
d4创建完成
❌ 析构函数

这时候就能看到原本该调用的移动构造和临时对象析构了。

内容的提问来源于stack exchange,提问作者Stephen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:30:05