返回值优化的原理是什么?对象创建未触发构造/赋值的疑问
嘿,这个问题问到点子上了!你碰到的是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的内存空间里了!
按照代码的字面逻辑,本该走这三步:
foo()里创建一个临时D对象(调用默认构造)- 把这个临时对象移动/拷贝给
d4(调用移动/拷贝构造) - 销毁
foo()里的临时对象(调用析构)
但RVO这个优化直接把步骤1和2合并了——编译器告诉foo():“别在你自己的栈帧里造对象了,直接去main()里d4的位置造!” 这样一来,中间的临时对象压根不存在,自然不需要调用任何拷贝/移动构造,也碰不到赋值运算符(赋值是给已存在的对象赋值,这里是直接构造新对象)。
返回值优化的核心原理和规则
RVO是C标准明确允许的特殊优化——哪怕它会改变程序的可观测行为(比如跳过了构造函数的打印),这也是C里少数几个允许“违背”代码字面逻辑的优化手段。它主要分两种情况:
- 纯RVO:当函数返回匿名临时对象时(比如
return D();),编译器几乎都会直接把对象构造在调用方的目标内存里。 - 命名返回值优化(NRVO):如果函数返回一个命名的局部变量(比如
D temp; return temp;),只要这个变量是函数内的纯局部对象,没有被复杂逻辑修改,编译器也能直接把它构造在调用方的目标位置。
几个关键注意点:
- RVO是编译器的可选优化,但主流编译器(GCC、Clang、MSVC)在默认或开启优化(比如
-O2)时都会自动执行。 - 如果你想强制禁用RVO观察原本的行为,可以用编译器选项:GCC/Clang加
-fno-elide-constructors,MSVC可以用/Od关闭所有优化,或者部分版本支持-fno-elide-constructors。 - 赋值场景(比如
D d4; d4 = foo();)才会触发赋值运算符,但有些编译器也会优化这一步,把它变成直接构造。
额外验证:如果禁用RVO会发生什么?
当你用-fno-elide-constructors编译上面的代码,输出会变成:
开始创建d4: ✅ 默认构造函数 🚀 移动构造函数 ❌ 析构函数 d4创建完成 ❌ 析构函数
这时候就能看到原本该调用的移动构造和临时对象析构了。
内容的提问来源于stack exchange,提问作者Stephen
相关产品推荐
相关产品推荐

