関数からローカルを返す多くの場合、RVOが起動します。ただし、std::move
を明示的に使用すると、少なくともRVOが発生しない場合は移動が強制されると考えましたが、可能な場合はRVOが引き続き適用されます。しかし、そうではないようです。
#include "iostream"
class HeavyWeight
{
public:
HeavyWeight()
{
std::cout << "ctor" << std::endl;
}
HeavyWeight(const HeavyWeight& other)
{
std::cout << "copy" << std::endl;
}
HeavyWeight(HeavyWeight&& other)
{
std::cout << "move" << std::endl;
}
};
HeavyWeight MakeHeavy()
{
HeavyWeight heavy;
return heavy;
}
int main()
{
auto heavy = MakeHeavy();
return 0;
}
このコードをVC++ 11とGCC 4.71でテストし、デバッグとリリース(-O2
)構成を行いました。コピートラクターが呼び出されることはありません。 move ctorは、デバッグ構成でVC++ 11によってのみ呼び出されます。実際、これらのコンパイラでは特に問題はないようですが、私の知る限りでは、RVOはオプションです。
ただし、move
を明示的に使用する場合:
HeavyWeight MakeHeavy()
{
HeavyWeight heavy;
return std::move(heavy);
}
移動トラクターは常に呼び出されます。したがって、「安全」にしようとすると、状況はさらに悪化します。
私の質問は:
-std::move
がRVOを妨げているのはなぜですか?
-「ベストを期待して」RVOに依存する方が良い場合はいつですか。また、std::move
を明示的に使用する必要があるのはいつですか。または、言い換えれば、RVOが適用されていない場合にコンパイラの最適化で機能させ、移動を強制するにはどうすればよいですか?
コピーと移動の省略が許可されるケースは、標準(バージョンN3690)のセクション12.8§31にあります。
特定の基準が満たされると、コピー/移動操作用に選択されたコンストラクターやオブジェクトのデストラクタに副作用がある場合でも、実装はクラスオブジェクトのコピー/移動構成を省略できます。このような場合、実装は、省略されたコピー/移動操作のソースとターゲットを、同じオブジェクトを参照する2つの異なる方法として扱い、そのオブジェクトの破棄は、2つのオブジェクトが後であったときに遅くなります。最適化なしで破棄されました。 copy elisionと呼ばれるこのコピー/移動操作の省略は、次の状況で許可されます(複数のコピーを削除するために組み合わせることができます)。
- クラスの戻り値の型を持つ関数の
return
ステートメント内。式がcv非修飾型と同じ非揮発性自動オブジェクト(関数またはcatch-clauseパラメーター以外)の名前である場合関数の戻り値の型。自動オブジェクトを関数の戻り値に直接作成することで、コピー/移動操作を省略できます。- [...]
- 参照(12.2)にバインドされていない一時クラスオブジェクトが同じcv-unqualifiedタイプのクラスオブジェクトにコピー/移動される場合、一時オブジェクトを直接ターゲットに構築することにより、コピー/移動操作を省略できます。省略されたコピー/移動の
- [...]
(省略した2つのケースは、例外オブジェクトをスローおよびキャッチするケースを参照していますが、最適化にとってそれほど重要ではないと考えています。)
したがって、returnステートメントでは、式がローカル変数の名前である場合に限り、コピーの省略が発生する可能性があります。を記述した場合std::move(var)
、それはもはや変数の名前ではありません。したがって、標準に準拠する必要がある場合、コンパイラは移動を回避できません。
Stephan T. Lavavejが Going Native 201 でこれについて話し、あなたの状況とstd::move()
を避ける理由をここで正確に説明しました。 38:04分に視聴を開始します。基本的に、戻り値型のローカル変数を返す場合、それは通常右辺値として扱われるため、デフォルトで移動が可能になります。
rVOが適用されていない場合にコンパイラの最適化で機能させ、移動を強制するにはどうすればよいですか?
このような:
HeavyWeight MakeHeavy()
{
HeavyWeight heavy;
return heavy;
}
リターンをムーブに変換することは必須です。