【C++】关于cpp的RVO/NRVO
RVO(Return Value Optimization,返回值优化)。NRVO(Named RVO,具名返回值优化)。
返回值直接构造:返回大对象时,编译器优化,让返回值直接在调用处构造,减少一次拷贝开销
C++17起,由于RVO,不需要在返回大对象时考虑将返回参数作为入参放在形参列表,这样代码可读性降低
- 符合RVO的条件:返回值和要返回的局部对象类型相同
A a;//a是一个big object
void func(A& a){...}
func(a);
//而是可以语义清晰的直接返回
A a = func();
一、为什么会有 RVO?——先看清“不优化”时有多浪费
假设我们有一个“很重”的对象 BigObject(拷贝起来很费劲):
struct BigObject {
int data[1000]{};
BigObject() { std::cout << "构造\n"; }
BigObject(const BigObject&) { std::cout << "拷贝\n"; }
~BigObject() { std::cout << "析构\n"; }
};
再来一个工厂函数:
BigObject makeObj() {
BigObject local; // 步骤1:在 makeObj 栈帧里构造
return local; // 步骤2:理论上要拷贝给调用者
}
如果没有优化,流程是:
- 在
makeObj里先给local分配一次内存,并构造一次。 - 返回时,再拷贝一份给调用者。
- 离开作用域时,
local析构一次。
也就是说:
“构造 1 次 + 拷贝 1 次 + 析构 1 次” —— 拷贝是白花花的开销。
二、RVO 做了什么?——把两次构造合并成一次
打开优化后(GCC/Clang 默认就打开),编译器会偷偷改写成下面这样:
// 伪代码:编译器视角
void makeObj(void* ret_addr) { // ret_addr 是调用者预留好的“目的地”
new (ret_addr) BigObject; // 直接在目的地构造
}
于是流程变成:
“构造 1 次 + 0 次拷贝 + 析构 1 次”
省掉了中间的拷贝/移动,这就是 RVO。
三、两个常见变体
-
无名 RVO(最经典)
返回一个“纯右值”临时对象:BigObject makeObj() { return BigObject{}; // 直接返回临时对象 } -
命名 RVO(NRVO)
返回一个“具名局部变量”:BigObject makeObj() { BigObject obj; // 具名 return obj; // NRVO 也可能触发 }
C++17 之前:两种都属于“允许优化”,编译器可以选做不做。
C++17 之后:第 1 种(返回纯右值)变成“强制优化”,标准规定必须省。
四、动手验证:把优化关掉/打开看输出
代码(gcc/clang 通用):
#include <iostream>
struct A {
A() { std::cout << "Ctor\n"; }
A(const A&) { std::cout << "Copy\n"; }
};
A get() { return A{}; } // 无名 RVO
int main() {
A a = get();
}
-
默认编译(打开优化)
g++ test.cpp -std=c++11 ./a.out输出:
Ctor只有一次构造,拷贝被省略。
-
关闭优化
g++ test.cpp -std=c++11 -fno-elide-constructors ./a.out输出:
Ctor Copy这回拷贝出现了。
五、C++17 的“强制 RVO”
从 C++17 开始,只要返回的是纯右值,即使拷贝/移动构造函数有副作用,也必须省略。
示例:
struct NoCopy {
NoCopy() = default;
NoCopy(const NoCopy&) = delete; // 删了拷贝
NoCopy(NoCopy&&) = delete; // 删了移动
};
NoCopy foo() {
return NoCopy{}; // C++17 合法,直接构造在目标位置
}
在 C++14 里,这段代码会因为删除拷贝/移动而编译失败;C++17 则 OK。
六、小结记忆
- RVO 的目标:把“构造+拷贝+析构”三件套,压缩成“构造+析构”两件套。
- 无名 RVO(返回临时对象)最容易触发;命名 RVO(返回局部变量)依赖编译器。
- 想自己验证:用
-fno-elide-constructors开关即可。 - C++17 起,纯右值返回场景下一定发生 RVO,标准强制。
一句话总结:
RVO 就是“编译器帮你在正确的内存位置上一次性把事情做完”,从而省掉来回搬运对象的成本。

浙公网安备 33010602011771号