RVO(返回值优化)的工作原理似乎有些混乱。
一个简单的例子:
#include <iostream>
struct A {
int a;
int b;
int c;
int d;
};
A create(int i) {
A a = {i, i+1, i+2, i+3 };
std::cout << &a << "\n";
return a;
}
int main(int argc, char*[]) {
A a = create(argc);
std::cout << &a << "\n";
}
及其在ideone的输出:
0xbf928684
0xbf928684
令人惊讶?
实际上,这就是 RVO 的效果:要返回的对象直接在调用者中就地构造。
如何 ?
传统上,调用者(main
此处)将在堆栈上为返回值保留一些空间:返回槽;被调用者(create
此处)被传递(以某种方式)返回槽的地址以将其返回值复制到其中。被调用者然后为它在其中构建结果的局部变量分配自己的空间,就像为任何其他局部变量一样,然后将其复制到return
语句的返回槽中。
当编译器从代码中推断出可以将变量直接构造到具有等效语义的返回槽中时(as-if 规则),就会触发 RVO。
请注意,这是一种常见的优化,它被标准明确列入白名单,编译器不必担心复制(或移动)构造函数的可能副作用。
什么时候 ?
编译器最有可能使用简单的规则,例如:
// 1. works
A unnamed() { return {1, 2, 3, 4}; }
// 2. works
A unique_named() {
A a = {1, 2, 3, 4};
return a;
}
// 3. works
A mixed_unnamed_named(bool b) {
if (b) { return {1, 2, 3, 4}; }
A a = {1, 2, 3, 4};
return a;
}
// 4. does not work
A mixed_named_unnamed(bool b) {
A a = {1, 2, 3, 4};
if (b) { return {4, 3, 2, 1}; }
return a;
}
在后一种情况 (4) 中,当返回时无法应用优化,A
因为编译器无法a
在返回槽中构建,因为它可能需要它来做其他事情(取决于布尔条件b
)。
因此,一个简单的经验法则是:
如果在声明之前没有声明其他返回槽的候选者,则应应用 RVO return
。