489

C++ 11 の右辺値参照と移動セマンティクスを理解しようとしています。

これらの例の違いは何ですか? また、ベクトル コピーを実行しないのはどれですか?

最初の例

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

std::vector<int> &&rval_ref = return_vector();

2 番目の例

std::vector<int>&& return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}

std::vector<int> &&rval_ref = return_vector();

3 番目の例

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}

std::vector<int> &&rval_ref = return_vector();
4

6 に答える 6

626

最初の例

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

std::vector<int> &&rval_ref = return_vector();

最初の例は、によってキャッチされる一時的なものを返しますrval_ref。その一時的なものは、その寿命がrval_ref定義を超えて延長され、値でキャッチしたかのように使用できます。これは次の例とよく似ています。

const std::vector<int>& rval_ref = return_vector();

rval_refただし、私の書き直しでは、const 以外の方法で使用できないことは明らかです。

2 番目の例

std::vector<int>&& return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}

std::vector<int> &&rval_ref = return_vector();

2 番目の例では、実行時エラーを作成しました。 関数内のrval_ref破壊されたへの参照を保持するようになりました。tmp運が良ければ、このコードはすぐにクラッシュします。

3 番目の例

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}

std::vector<int> &&rval_ref = return_vector();

3 番目の例は、最初の例とほぼ同じです。std::moveonは不要であり、tmp戻り値の最適化を阻害するため、実際にはパフォーマンスのペシミゼーションになる可能性があります。

あなたがしていることをコーディングする最良の方法は次のとおりです。

ベスト プラクティス

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

std::vector<int> rval_ref = return_vector();

つまり、C++03 の場合と同じです。 tmpreturn ステートメントでは暗黙的に右辺値として扱われます。return-value-optimization (no copy, no move) を介して返されるか、RVO を実行できないとコンパイラが判断した場合は、ベクトルの移動コンストラクタを使用して return を実行します。RVO が実行されず、返された型に移動コンストラクターがない場合にのみ、コピー コンストラクターが返されます。

于 2011-02-13T20:52:04.650 に答える
45

どちらもコピーされませんが、2 番目は破棄されたベクターを参照します。名前付き右辺値参照は、通常のコードにはほとんど存在しません。C++03 でコピーを作成するのと同じ方法で作成します。

std::vector<int> return_vector()
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

std::vector<int> rval_ref = return_vector();

今を除いて、ベクトルは移動されます。クラスのユーザーは、ほとんどの場合、その右辺値参照を扱いません。

于 2011-02-13T20:31:35.860 に答える
16

簡単な答えは、通常の参照コードと同じように右辺値参照のコードを記述し、99% の時間、精神的に同じように扱う必要があるということです。これには、参照を返すことに関するすべての古い規則が含まれます (つまり、ローカル変数への参照を決して返さない)。

std::forward を利用する必要があり、左辺値または右辺値参照のいずれかを取る汎用関数を記述できる必要があるテンプレート コンテナ クラスを記述していない限り、これは多かれ少なかれ真実です。

移動コンストラクターと移動代入の大きな利点の 1 つは、それらを定義すると、RVO (戻り値の最適化) と NRVO (名前付きの戻り値の最適化) の呼び出しに失敗した場合に、コンパイラーがそれらを使用できることです。これは、コンテナや文字列などの高価なオブジェクトをメソッドから効率的に値で返すにはかなり大きいです。

右辺値参照で興味深い点は、それらを通常の関数の引数としても使用できることです。これにより、const 参照 (const foo& other) と右辺値参照 (foo&& other) の両方のオーバーロードを持つコンテナーを作成できます。単なるコンストラクター呼び出しで引数を渡すには扱いにくい場合でも、それを行うことができます。

std::vector vec;
for(int x=0; x<10; ++x)
{
    // automatically uses rvalue reference constructor if available
    // because MyCheapType is an unamed temporary variable
    vec.push_back(MyCheapType(0.f));
}


std::vector vec;
for(int x=0; x<10; ++x)
{
    MyExpensiveType temp(1.0, 3.0);
    temp.initSomeOtherFields(malloc(5000));

    // old way, passed via const reference, expensive copy
    vec.push_back(temp);

    // new way, passed via rvalue reference, cheap move
    // just don't use temp again,  not difficult in a loop like this though . . .
    vec.push_back(std::move(temp));
}

STL コンテナは更新され、ほぼすべてのもの (ハッシュ キーと値、ベクトルの挿入など) のオーバーロードを移動できるようになりました。これらが最も多く見られる場所です。

それらを通常の関数に使用することもできます。右辺値参照引数のみを指定すると、呼び出し元にオブジェクトを作成させ、関数に移動させることができます。これは単なる例にすぎませんが、私のレンダリング ライブラリでは、読み込まれたすべてのリソースに文字列を割り当てて、デバッガーで各オブジェクトが何を表しているかを簡単に確認できるようにしています。インターフェイスは次のようなものです。

TextureHandle CreateTexture(int width, int height, ETextureFormat fmt, string&& friendlyName)
{
    std::unique_ptr<TextureObject> tex = D3DCreateTexture(width, height, fmt);
    tex->friendlyName = std::move(friendlyName);
    return tex;
}

これは「漏れやすい抽象化」の形式ですが、ほとんどの場合、すでに文字列を作成しなければならなかったという事実を利用して、さらに別のコピーを作成することを避けることができます。これは正確には高性能なコードではありませんが、人々がこの機能に慣れるにつれて可能性を示す良い例です。このコードでは、変数が呼び出しに対して一時的であるか、std::move が呼び出される必要があります。

// move from temporary
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, string("Checkerboard"));

また

// explicit move (not going to use the variable 'str' after the create call)
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, std::move(str));

また

// explicitly make a copy and pass the temporary of the copy down
// since we need to use str again for some reason
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, string(str));

しかし、これはコンパイルされません!

string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, str);
于 2011-07-10T00:56:07.247 に答える
4

それ自体は答えではなく、ガイドラインです。T&&ほとんどの場合、ローカル変数を宣言することにはあまり意味がありません( で行ったようにstd::vector<int>&& rval_ref)。型メソッドで使用するには、引き続きstd::move()それらを使用する必要があります。foo(T&&)関数からそのようなものを返そうとするrval_refと、標準の破壊された一時的なフィアスコへの参照が得られるという、すでに述べた問題もあります。

ほとんどの場合、次のパターンを使用します。

// Declarations
A a(B&&, C&&);
B b();
C c();

auto ret = a(b(), c());

返された一時オブジェクトへの参照を保持しないため、移動したオブジェクトを使用したい (経験の浅い) プログラマーのエラーを回避できます。

auto bRet = b();
auto cRet = c();
auto aRet = a(std::move(b), std::move(c));

// Either these just fail (assert/exception), or you won't get 
// your expected results due to their clean state.
bRet.foo();
cRet.bar();

オブジェクトに移動できる非一時T&&オブジェクトへの参照である を関数が実際に返すケースが明らかにあります (かなりまれですが) 。

RVO に関して: これらのメカニズムは一般的に機能し、コンパイラはコピーを適切に回避できますが、リターン パスが明らかでない場合 (例外、if返される名前付きオブジェクトを決定する条件、およびおそらく他の結合) では、rref が救世主です (潜在的にそれ以上の場合でも)。高い)。

于 2013-02-26T07:46:40.867 に答える
3

それらのどれも余分なコピーを行いません。RVO が使用されていない場合でも、新しい標準では、リターンを行うときにコピーよりもムーブ コンストラクションが優先されると記載されていると思います。

ローカル変数への参照を返しているため、2番目の例では未定義の動作が発生すると思います。

于 2011-02-13T20:40:33.517 に答える