RVO优化
1. 名词与结论
- RVO(Return Value Optimization):返回临时对象时,编译器省掉临时对象与目标对象之间的拷贝/移动构造。
- NRVO(Named RVO):返回具名局部对象时的优化(
return obj;
)。 - C++17 起的“强制性拷贝省略”:在某些情形下(见 §2),标准保证不产生临时与拷贝/移动;不是“可选优化”,而是语义的一部分。
2. 何时一定省掉拷贝(C++17 保证)
以下情形创建的是目标对象本体,不会先构造临时再搬运:
-
直接返回临时
T make() { return T(args); } // 保证无拷贝/无移动
-
返回花括号临时
struct S { int a; double b; }; S make() { return {1, 2.0}; } // 同样保证
-
用函数返回值初始化对象(同类型)
T x = make(); // prvalue 直接用于构造 x,无中间临时
启示:尽量返回临时或简单构造式;C++17 起无需为“省一次移动”做多余技巧。
3. NRVO(具名变量)什么时候能触发
NRVO 不是强制,但主流编译器(GCC/Clang/MSVC)几乎总能触发,条件包括:
- 返回的对象是同一作用域的非
volatile
局部自动对象; - 返回语句在所有路径上都返回同一个对象(越一致越容易触发)。
典型可触发:
T make(bool f) {T a, b;if (f) return a;else return a; // 所有路径同一个 a,更容易
}
可能阻碍 NRVO 的情形(编译器可能放弃):
- 分支返回不同的具名对象(a 或 b);
- 对返回对象取地址/绑定到引用逃逸;
volatile
对象、复杂异常路径等。
启示:尽量统一返回同一个具名对象;或直接用 §2 的“返回临时”。
4. 不要写的反优化:return std::move(obj);
- 对具名局部变量
obj
:std::move(obj)
会阻碍 NRVO,让编译器退化为移动构造(甚至在某些场景变成拷贝)。 - 编译器会给出
-Wpessimizing-move
(Clang/GCC)警告。
正确写法
T make() {T obj;// ... 填充 obj ...return obj; // 让 NRVO 尽量触发;不要 std::move(obj)
}
5. 与移动语义的关系
- RVO/NRVO 的收益 > 移动:因为连移动构造也被消除了(直接在目标位置构造)。
- C++17 之后,返回临时常常无需考虑“要不要
std::move
”,因为直接就不产生移动。
6. 设计与重构建议(实战导向)
-
返回按值(by value)——放心用
现代 C++(17+)下,只要返回的是临时或满足 NRVO 条件的局部,性能非常好;接口更简单、安全。 -
工厂函数优先返回临时
static T make_xxx(args...) {return T(args...); // 保证省略 }
-
统一返回点
需要做分支初始化时,可以用一处返回以帮助 NRVO:T make(bool f) {T obj = f ? T(1) : T(2);// ... 进一步配置 obj ...return obj; // 触发 NRVO 的机会更高 }
-
容器就地构造
RVO/NRVO 解决“函数返回值”的搬运;而容器内部建议emplace_back(...)
,避免中间对象:vec.emplace_back(args...); // 直接就地构造
-
避免返回参数/捕获变量
函数参数、lambda 捕获的对象不是局部自动对象,不享受 NRVO。T f(T x) { return x; } // 常规是移动/拷贝,非 NRVO 对象
7. 诊断与验证
- 编译期警告:
-Wpessimizing-move
(避免“画蛇添足”的std::move
)。 - 观察构造次数:可在类型的拷贝/移动构造里打印日志对比
-O2
与-fno-elide-constructors
。 - 主流优化开关:
-O2/-O3
;无需专门打开 RVO 开关。
调试 RVO 行为时可临时加:-fno-elide-constructors
(GCC/Clang)。
8. 常见 Q&A
Q:返回 std::optional<T>
会影响 RVO 吗?
A:return std::optional<T>{T(args)};
中 T(args)
的构造仍受 C++17 prvalue 规则优化;同时可改为 return std::optional<T>(std::in_place, args...)
进一步避免中间层次。
Q:聚合类型/结构体大对象能否放心按值返回?
A:C++17 起可放心。按值返回+RVO/NRVO 通常等价于“直接在调用方栈上构造”,无多余移动。
Q:异常会破坏 RVO/NRVO 吗?
A:标准允许实现自由,但主流编译器在无复杂控制流时仍能很好地做 NRVO;返回临时的强制省略照常生效。