2013-10-09 12 views
35

多くの場合、関数からローカルを返すときにRVOが起動します。しかし、std::moveを明示的に使用するとRVOが発生しない場合でもRVOは可能な限り適用されます。しかし、そうではないようです。なぜstd :: moveは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)設定でこのコードをテストしました。コピーctorは決して呼び出されません。 move ctorは、デバッグ設定でVC++ 11によってのみ呼び出されます。実際、これらのコンパイラではすべてがうまくいくようですが、私の知る限り、RVOはオプションです。

しかし、私は明示的にmoveを使用する場合:

HeavyWeight MakeHeavy() 
{ 
    HeavyWeight heavy; 
    return std::move(heavy); 
} 

移動CTORが常に呼び出されます。だから、それを "安全"にしようとすると悪化します。

私の質問は次のとおりです:
- なぜstd::moveはRVOを防止しますか?
- 「ベストを願って」RVOに頼る方が良いのはいつですか。また、いつ明示的にstd::moveを使うべきですか?つまり、RVOが適用されていない場合、どのようにしてコンパイラの最適化がその作業を行い、依然として実行を許可することができますか?

+4

最近、なぜ人々は「最善の希望」について話しているのですか?どのような種類のコンパイラを使用しているのですか?C++ 11のサポートはありますが、RVOは正しくできませんか? –

+1

コピーエリジョン(RVOの背後にあるメカニズム)は、特定の厳しい条件の下でのみ許可されます。 'std :: move'を書くことは、それらの条件が満たされないようにします。 –

+2

@KerrekSBそしてこれらの条件はstd :: moveによって防止されています...? – cdoubleplusgood

答えて

25

例は、標準(バージョンN3690)のセクション12.8§31に見出される許される:

特定の基準が満たされた場合、実装はコピー/移動を省略することが許可されていますコピー/移動操作のために選択されたコンストラクタおよび/またはオブジェクトのデストラクタが副作用を有する場合であっても、クラスオブジェクトの構築が可能である。このような場合、実装は、省略されたコピー/移動操作のソースとターゲットを、同じオブジェクトを参照する2つの異なる方法として扱い、そのオブジェクトの破壊は、2つのオブジェクトが最適化せずに破壊されました。コピー/移動操作のこのエリジオン、コピーの省略と呼ばれるが、(複数のコピーを排除するために組み合わせることができる)は、以下の状況で許可されている:

  • 関数でreturnステートメントでクラス戻り型と、式が関数の戻り値の型と同じcv-unqualified型を持つ不揮発性の自動オブジェクト(関数またはcatch節パラメータ以外)の名前である場合、自動オブジェクトを構築することによってコピー/移動操作を省略することができますファンクションの戻り値に直接入力します。
  • [...]
  • (12.2)にバインドされたオブジェクトは、同じcv-unqualified型のクラスオブジェクトにコピー/移動されるため、コピー/移動の省略対象のターゲットに直接テンポラリオブジェクトを直接組み込むことによってコピー/ムーブ操作を省略できます
  • [...return文のコピーの省略でしたがって]

(私は左の2例は、私は最適化のためにそれほど重要と考える例外オブジェクトを投げるとキャッチの場合を参照してください。)

のみ発生する可能性があり、式がの場合、ローカル変数の名前です。std::move(var)と書くと、それはもはや変数の名前ではありません。したがって、標準に準拠する必要がある場合、コンパイラは移動を削除できません。

Stephan T. LavavejはこれについてGoing Native 2013でお話ししました。あなたの状況と避ける理由をここではstd::move()と説明しました。分38:04で視聴を開始する。基本的には、戻り値の型のローカル変数を返すときは通常はrvalueとして扱われ、デフォルトではmoveが可能になります。

+0

これは私が望んでいた答えです。私は状況に満足していないが、私はよく理解する。 – cdoubleplusgood

+5

'return std :: move'を省略できるように修正するべきでしょう。関数の参照戻り値が関数の特定の参照入力値と同じであることが保証されているとC++に伝えることができれば、興味深い結果がいくつか開かれる可能性があります。 (一時的な引数を一時的に返さずに一時的な入力引数を生涯拡張して2つの関数を呼び出す:2番目の関数はIMHOがより重要です)。 – Yakk

+0

はい、私はこれを逃すことができないという理性を理解していません。コンパイラの作者にもっと時間を許すのはちょっと難しいですか? – Adrian

13

RVOが適用されていない場合、どのようにコンパイラの最適化がその作業を行い、依然として実行を許可できますか?このよう

:移動にリターンを変換

HeavyWeight MakeHeavy() 
{ 
    HeavyWeight heavy; 
    return heavy; 
} 

は必須です。エリジオンをコピーして移動

+0

ローカルを返すことは最悪の場合の動きであることが保証され、RVOが最良の場合に適用されますか? – cdoubleplusgood

+2

@cdoubleplusgood:はい。 – Xeo

+0

私は 'return std :: move'のためにgrep'pingをする必要があると思います;) – goji

関連する問題