web-dev-qa-db-ja.com

std :: moveがRVOを妨げるのはなぜですか?

関数からローカルを返す多くの場合、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が適用されていない場合にコンパイラの最適化で機能させ、移動を強制するにはどうすればよいですか?

52
cdoubleplusgood

コピーと移動の省略が許可されるケースは、標準(バージョン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分に視聴を開始します。基本的に、戻り値型のローカル変数を返す場合、それは通常右辺値として扱われるため、デフォルトで移動が可能になります。

34
Ralph Tandetzky

rVOが適用されていない場合にコンパイラの最適化で機能させ、移動を強制するにはどうすればよいですか?

このような:

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

リターンをムーブに変換することは必須です。

16