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
ここ) は戻り値用にスタックにいくらかのスペースを確保します: return slot ; 呼び出し先(create
ここでは)には、戻り値をコピーする戻りスロットのアドレスが(何らかの方法で)渡されます。次に、呼び出し先は、他のローカル変数と同様に、結果を構築するローカル変数に独自のスペースを割り当て、それをreturn
ステートメントの戻りスロットにコピーします。
RVO は、同等のセマンティクス (as-if ルール) を使用して、変数をリターン スロットに直接構築できることをコンパイラがコードから推測したときにトリガーされます。
これは非常に一般的な最適化であるため、標準によって明示的にホワイトリストに登録されており、コンパイラはコピー (または移動) コンストラクターの副作用について心配する必要がないことに注意してください。
いつ ?
コンパイラは、次のような単純なルールを使用する可能性が最も高いです。
// 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 should be applied if no other candidate for the return slot has been declared prior to the return
statement.