【发布时间】:2011-11-27 14:42:21
【问题描述】:
RVO (Return Value Optimization) 是否保证或适用于 C++ 编译器(特别是 GCC)中的所有对象和情况?
如果答案是“否”,那么对类/对象进行这种优化的条件是什么?如何强制或鼓励编译器对特定返回值执行 RVO?
【问题讨论】:
标签: c++ optimization gcc
RVO (Return Value Optimization) 是否保证或适用于 C++ 编译器(特别是 GCC)中的所有对象和情况?
如果答案是“否”,那么对类/对象进行这种优化的条件是什么?如何强制或鼓励编译器对特定返回值执行 RVO?
【问题讨论】:
标签: c++ optimization gcc
gcc 编译器中的所有对象都保证 RVO(返回值优化)吗?
永远保证没有优化(尽管 RVO 相当可靠,但确实存在 some cases that throw it off)。
如果答案是“否”,那么这个类/对象的优化条件是什么?
一个从你那里刻意抽象出来的实现细节。
请不要知道也不关心这个。
【讨论】:
我没有是或否的答案,但你说如果你正在寻找的优化得到保证,你可以编写更少的代码行。
如果你写了你需要写的代码,程序就会一直运行,如果优化在那里,它会运行得更快。如果确实存在优化在逻辑中“填补空白”而不是代码机制并使其工作的情况,或者彻底改变逻辑,那似乎是我想要修复的错误而不是我想依赖或利用的实现细节。
【讨论】:
返回值优化可以总是应用,不能普遍应用的是命名返回值优化。基本上,为了进行优化,编译器必须知道在构造对象的地方将返回什么对象。
在 RVO(返回一个临时值)的情况下,该条件很容易满足:对象是在 return 语句中构造的,并且它被返回了。
对于 NRVO,您必须分析代码以了解编译器是否知道该信息。如果函数的分析很简单,编译器很可能会对其进行优化(例如,不包含条件的单个 return 语句;同一对象的多个 return 语句;编译器知道的多个 return 语句,如T f() { if (condition) { T r; return r; } else { T r2; return r2; } } r 或 r2 将被退回...)
请注意,您只能在简单的情况下假设优化,具体来说,维基百科中的示例实际上可以由足够智能的编译器进行优化:
std::string f( bool x ) {
std::string a("a"), b("b");
if ( x ) return a;
else return b;
}
可以被编译器改写成:
std::string f( bool x ) {
if ( x ) {
std::string a("a"), b("b");
return a;
} else {
std::string a("a"), b("b");
return b;
}
}
此时编译器可以知道在第一个分支中a 将被构造来代替返回的对象,并且在第二个分支中同样适用于b。但我不会指望这一点。如果代码很复杂,假设编译器无法产生优化。
编辑:有一种情况我没有明确提到,不允许编译器(在大多数情况下即使允许,它也不可能这样做)优化掉副本从函数的参数到返回语句:
T f( T value ) { return value; } // Cannot be optimized away --but can be converted into
// a move operation if available.
【讨论】:
value 不是通过值传递的临时方式,因此完全符合 RVO 的条件吗?
value 不会超过这个功能,但它不是暂时的(没关系)。该语言没有规定要删除该副本,这就是注释的原因。至于为什么,复制省略是通过直接在副本将拥有的内存上创建副本来完成的,那么就不需要复制了。常见的 ABI 让调用者为返回的对象保留空间,它也是复制参数的调用者。但是在单独编译的语言中,调用者不知道函数是否会返回该参数 [...]
致 Jesper:如果要构建的对象很大,则可能需要避免复制(或至少非常可取)。
如果发生 RVO,则避免复制,您无需再编写任何代码行。
如果没有,您将不得不手动完成,自己编写额外的脚手架。这可能会涉及到提前指定一个缓冲区,迫使你为这个空的(可能是无效的,你可以看到它不干净)对象编写一个构造函数和一个“构造”这个无效对象的方法。
因此,如果有保证,它可以减少我的代码行数。不是吗?”并不意味着马苏德是个白痴。然而,对他来说不幸的是,不能保证 RVO。您必须测试它是否发生,如果没有,编写脚手架并污染您的设计。不可能是疱疹的。
【讨论】:
移动语义(C++11 的新特性)是您问题的解决方案,它允许您显式使用Type(Type &&r);(移动构造函数),而不是Type(const Type &r)(复制构造函数)。
例如:
class String {
public:
char *buffer;
String(const char *s) {
int n = strlen(s) + 1;
buffer = new char[n];
memcpy(buffer, s, n);
}
~String() { delete [] buffer; }
String(const String &r) {
// traditional copy ...
}
String(String &&r) {
buffer = r.buffer; // O(1), No copying, saves time.
r.buffer = 0;
}
};
String hello(bool world) {
if (world) {
return String("Hello, world.");
} else {
return String("Hello.");
}
}
int main() {
String foo = hello();
std::cout <<foo.buffer <<std::endl;
}
而且这不会触发复制构造函数。
【讨论】:
return-ing 对象会使其构造一个新对象(通过复制或移动),而不是临时对象的引用。你说的只有当返回类型是引用或指针时才会发生。
std::move。这样做没有任何好处,实际上可能prevent the compiler 可以利用 RVO。具有讽刺意味的是,这回答了“RVO 是否适用于所有对象?”的问题。通过产生一个不允许 RVO 的情况。