17

C++Next のブログ投稿では、次のように述べています。

A compute(…)
{
    A v;
    …
    return v;
}

アクセス可能なコピーまたは移動コンストラクターがある場合A、コンパイラーはコピーを除外することを選択できます。それ以外の場合A、移動コンストラクターがある場合は移動されvます。それ以外の場合A、コピー コンストラクターがある場合vはコピーされます。そうしないと、コンパイル時エラーが発生します。

std::move コンパイラはユーザーにとって最適な選択を見つけられるため、常に値を返さないようにする必要があると考えました。しかし、ブログ投稿の別の例では

Matrix operator+(Matrix&& temp, Matrix&& y)
  { temp += y; return std::move(temp); }

関数内で左辺値として扱われるstd::move必要があるため、ここで必要です。y

ああ、このブログ投稿を読んだ後、私の頭はほとんど吹き飛ばされました。私はその理由を理解しようと最善を尽くしましたが、勉強すればするほど混乱していきました。を使用して値を返す必要があるのはなぜstd::moveですか?

4

4 に答える 4

25

だから、あなたが持っているとしましょう:

A compute()
{
  A v;
  …
  return v;
}

そして、あなたはやっています:

A a = compute();

この式には 2 つの転送 (コピーまたは移動) が含まれます。最初vに、関数内で によって示されるオブジェクトを関数の結果、つまりcompute()式によって渡された値に転送する必要があります。これを Transfer 1 と呼びましょう。次に、この一時オブジェクトを転送して、aTransfer 2 で示されるオブジェクトを作成します。

多くの場合、Transfer 1 と 2 の両方をコンパイラで省略できます。オブジェクトvは の場所に直接構築され、a転送は必要ありません。この例では、返されるオブジェクトが名前付きであるため、コンパイラは転送 1 の名前付き戻り値の最適化を使用する必要があります。ただし、コピー/移動省略を無効にすると、各転送には、A のコピー コンストラクターまたはその移動コンストラクターの呼び出しが含まれます。最近のほとんどのコンパイラでは、コンパイラはvそれが破棄されようとしていることを認識し、最初にそれを戻り値に移動します。次に、この一時的な戻り値が に移動されaます。A移動コンストラクターがない場合は、代わりに両方の転送でコピーされます。

次に見てみましょう:

A compute(A&& v)
{
  return v;
}

返される値は、関数に渡される参照から取得されます。コンパイラはv、それが一時的なものであり、そこから移動しても問題ないと想定しているだけではありません1。この場合、転送 1 はコピーになります。その後、Transfer 2 が移動になります。返される値はまだ一時的なものなので問題ありません (参照は返されませんでした)。しかし、移動元のオブジェクトを取得したことがわかっvているため、パラメーターは右辺値参照であるため、次のようにコンパイラーに一時的なものとして扱うように明示的に指示できstd::moveます。

A compute(A&& v)
{
  return std::move(v);
}

これで、Transfer 1 と Transfer 2 の両方が Move になります。


1vコンパイラがとして定義された を右辺値として自動的に扱わない理由はA&&、安全上の理由の 1 つです。それを理解するのはあまりにもばかげているだけではありません。オブジェクトに名前が付けられると、コード全体で何度も参照できます。検討:

A compute(A&& a)
{
  doSomething(a);
  doSomethingElse(a);
}

aが自動的に右辺値として扱われる場合doSomething、その内臓を自由に引き裂くことができます。つまり、a渡されたdoSomethingElseものが無効になる可能性があります。doSomething引数を値で受け取ったとしても、オブジェクトは移動されるため、次の行では無効になります。この問題を回避するために、名前付き右辺値参照は左辺値です。つまりdoSomething、呼び出されたときaに、左辺値参照だけで取得されていない場合は、最悪でもコピーされます。次の行でも有効です。

compute「わかりました。この値が一時的なオブジェクトであることは確かにわかっているので、この値を移動できるようにします」と言うのは、の作成者次第です。と言ってこれを行いますstd::move(a)。たとえばdoSomething、コピーを提供doSomethingElseしてから、そこから移動できるようにすることができます。

A compute(A&& a)
{
  doSomething(a);
  doSomethingElse(std::move(a));
}
于 2012-11-17T13:37:29.097 に答える
5

1 つ目は、移動よりも優れた NVRO を利用しています。安いものより優れたコピーはありません。

2 つ目は NVRO を利用できません。省略がないと仮定するreturn temp;と、コピー コンストラクターreturn std::move(temp);が呼び出され、ムーブ コンストラクターが呼び出されます。さて、私はこれらのどちらも省略される可能性が等しいと信じています。したがって、省略されていない場合は、std::move.

于 2012-11-17T13:20:38.947 に答える
5

関数結果の暗黙的な移動は、自動オブジェクトに対してのみ可能です。右辺値参照パラメーターは自動オブジェクトを示さないため、その場合は移動を明示的に要求する必要があります。

于 2012-11-17T13:15:31.020 に答える