- RVO (Return Value Optimization) :是关于函数返回值的优化。它能避免为函数返回的临时对象创建拷贝。
- Pass-by-Value and Move :是关于函数参数传递的优化策略。它利用移动语义来接收参数,避免不必要的拷贝。
它们的应用场景完全不同,一个是"出",一个是"进"。下面我们来详细分解一下。
1. RVO/NRVO:函数返回时的优化
RVO (Return Value Optimization) 和 NRVO (Named Return Value Optimization) 是编译器的一种优化技术,它可以在函数返回一个对象时,直接将这个对象构造在调用者指定的内存位置上,从而完全省略掉一次拷贝(或移动)构造。
示例:
cpp
std::string create_string() {
std::string s = "hello world";
return s; // 这里可能会发生 NRVO
}
int main() {
std::string main_s = create_string();
}
没有 RVO/NRVO 的逻辑步骤:
- 在
create_string函数内部,创建一个局部std::string s。 return s;时,将s拷贝/移动到一个临时的返回值对象中。- 在
main函数中,main_s通过这个临时返回值对象进行拷贝/移动构造。
有 RVO/NRVO 的实际步骤:
- 编译器非常聪明,它发现
create_string的返回值将要初始化main_s。 - 于是,编译器直接在
main_s的内存空间上构造了那个值为"hello world"的字符串。 - 中间的局部变量
s和临时返回值对象都"消失"了。没有发生任何拷贝或移动。
关键点:RVO/NRVO 是一种"消除"操作,它让拷贝/移动从始至终都没有发生。从 C++17 开始,在某些情况下,这种优化已经是强制性的。
2. Pass-by-Value and Move:函数参数传递的优化
这是我们在 EventLoopThreadPool 构造函数中讨论的场景。我们来看一下这个构造函数:
cpp
EventLoopThreadPool::EventLoopThreadPool(IEventLoop* baseLoop, std::string nameArg)
: baseLoop_(baseLoop),
name_(std::move(nameArg)), // 从参数移动到成员变量
started_(false),
numThreads_(0),
next_(0)
{
}
这里我们分析两种情况:
情况 A:传递一个右值(临时对象)
当你这样调用时:
EventLoopThreadPool pool(loop, "my-pool");
这里的 "my-pool" 是一个 const char*,它会隐式地构造一个 std::string 的临时对象(这是一个右值)。
逻辑步骤:
- 一个
std::string临时对象被创建。 - 这个临时对象被 移动构造 到参数
nameArg中。(注意 :编译器通常会在这里进行一次类似 RVO 的优化,直接在nameArg的位置上构造临时对象,从而省略这次移动)。 - 在构造函数的初始化列表中,
nameArg通过std::move移动构造 到成员变量name_中。
最终结果: 1 次构造 + 1 次移动。非常高效。
情况 B:传递一个左值(命名对象)
当你这样调用时:
cpp
std::string pool_name = "my-pool";
EventLoopThreadPool pool(loop, pool_name);
这里的 pool_name 是一个左值。
逻辑步骤:
pool_name被 拷贝构造 到参数nameArg中。因为我们不能修改pool_name本身。- 在构造函数的初始化列表中,
nameArg通过std::move移动构造 到成员变量name_中。
最终结果: 1 次拷贝 + 1 次移动。
关键点 :这种方式是通过 移动 来"窃取"参数
nameArg的资源,而不是像 RVO 那样完全"消除"一个对象。它为处理左值和右值提供了一个统一且高效的接口。
总结与对比
| 特性 | RVO / NRVO | Pass-by-Value and Move |
|---|---|---|
| 场景 (Context) | 函数返回值时 | 函数参数传递时(特别是对于 "sink" 型参数,即要存起来的参数) |
| 机制 (Mechanism) | 消除 (Elision) 拷贝/移动,直接在调用者内存中构造对象。 | 通过移动 (Move) 来转移资源,避免深拷贝。 |
| 效果 | 理想情况下,没有额外的构造、拷贝或移动。 | 对于右值,有一次移动;对于左值,有一次拷贝和一次移动。 |
| 本质 | 编译器的"魔法",它改变了对象的构造地点。 | C++11 语言特性(右值引用和移动语义)的一种编程模式。 |
当您传递一个右值(例如 "my-pool")时,它和 RVO 的目标都是避免昂贵的拷贝,但实现路径不同:
- RVO 是在函数返回时,通过"消除"操作实现的。
- 我们的构造函数参数优化,是在函数调用 时,通过移动语义来高效接收这个右值。