57

デフォルトの移動セマンティクスをサポートするタイプTについて考えてみます。以下の関数も考慮してください。

T f() {
   T t;
   return t;
}

T o = f();

古いC++03では、一部の最適でないコンパイラは、コピーコンストラクタを2回呼び出す場合がありました。1つは「returnobject」用で、​​もう1つは。用oです。

c ++ 11では、t内部f()が左辺値であるため、これらのコンパイラーは、以前と同様にコピーコンストラクターを1回呼び出してから、oの移動コンストラクターを呼び出す場合があります。

t最初の「余分なコピー」を回避する唯一の方法は、戻ったときに移動することであると述べるのは正しいですか?

T f() {
   T t;
   return std::move(t);
}
4

3 に答える 3

55

いいえreturn。ステートメント内のローカル変数がコピーの省略に適格である場合は常に、右辺値参照にバインドされるため、どのコンストラクターが適格であるかに関して、例とreturn t;同じです。return std::move(t);

ただし、これreturn std::move(t); により、コンパイラがコピーの省略を実行できなくなることに注意してくださいreturn t。そうではないので、後者が好ましいスタイルです。[訂正してくれた@Johannesに感謝します。]コピーの省略が発生した場合、移動構造を使用するかどうかの問題が論点になります。

標準の12.8(31、32)を参照してください。

Tまた、アクセス可能なコピーがあるが削除されたmoveコンストラクターがある場合はreturn t;、moveコンストラクターを最初に検討する必要があるため、コンパイルされないことにも注意してください。あなたはreturn static_cast<T&>(t);それを機能させるために何かを言わなければならないでしょう:

T f()
{
    T t;
    return t;                 // most likely elided entirely
    return std::move(t);      // uses T::T(T &&) if defined; error if deleted or inaccessible
    return static_cast<T&>(t) // uses T::T(T const &)
}
于 2012-08-05T16:04:57.243 に答える
18

いいえ。ベスト プラクティスは直接return t;です。

クラスTに削除されていないムーブ コンストラクターがあり、noticeがコピー省略の対象とtなるローカル変数である場合、ムーブは返されたオブジェクトを同様に構築します。ただし、エリジョンをコピー/移動する資格があるため、構築は省略できますが、常に移動コンストラクターを使用して戻り値を構築します。return treturn std::move(t);return t;return std::move(t)

クラス内のムーブ コンストラクターTが削除されているが、コピー コンストラクターが使用可能な場合、return std::move(t);コンパイルされませんが、コピー コンストラクターreturn t;を使用してコンパイルされます。言及された@Kerrekとは異なりt、右辺値参照にバインドされていません。コピー省略の対象となる戻り値には、2 段階のオーバーロード解決があります。最初に移動してからコピーを試してください。移動とコピーの両方が省略される可能性があります。

class T
{
public:
    T () = default;
    T (T&& t) = delete;
    T (const T& t) = default;
};

T foo()
{
    T t;
    return t;                   // OK: copied, possibly elided
    return std::move(t);        // error: move constructor deleted
    return static_cast<T&>(t);  // OK: copied, never elided
}

式が左辺値であり、コピー省略の対象にならない場合return(非ローカル変数または左辺値式を返す可能性が最も高い)、それでもコピーを避けたい場合は、std::moveが役立ちます。ただし、ベスト プラクティスは、コピーの省略を可能にすることです。

class T
{
 public:
    T () = default;
    T (T&& t) = default;
    T (const T& t) = default;
};

T bar(bool k)
{
    T a, b;
    return k ? a : b;            // lvalue expression, copied
    return std::move(k ? a : b); // moved
    if (k)
        return a;                // moved, and possibly elided
    else
        return b;                // moved, and possibly elided
}

標準の 12.8(32) では、このプロセスについて説明しています。

12.8 [class.copy]

32 コピー操作の省略の基準が満たされているか、ソース オブジェクトが関数パラメーターであり、コピーされるオブジェクトが左辺値によって指定されているという事実を除いて満たされる場合、コピーのコンストラクターを選択するためのオーバーロード解決オブジェクトが右辺値によって指定されたかのように最初に実行されます。オーバーロードの解決が失敗した場合、または選択されたコンストラクターの最初のパラメーターの型がオブジェクトの型 (おそらく cv 修飾) への右辺値参照ではない場合、オブジェクトを左辺値と見なしてオーバーロードの解決が再度実行されます。[ 注: この 2 段階のオーバーロード解決は、コピーの省略が発生するかどうかに関係なく実行する必要があります。省略が実行されない場合に呼び出されるコンストラクターを決定し、呼び出しが省略された場合でも、選択されたコンストラクターにアクセスできる必要があります。—終わりのメモ]

于 2013-10-31T04:31:25.767 に答える
3

わかりました、これについてコメントをドロップしたいと思います。std::moveこの質問 (および回答) により、return ステートメントで指定する必要はないと思いました。しかし、コードを扱っている間、私は別の教訓を考えていました。

だから、私は一時的なものを取り、それを返すだけの関数(実際には特殊化です)を持っています。(汎用関数テンプレートは他の処理を行いますが、特殊化は ID 操作を行います)。

template<>
struct CreateLeaf< A >
{
  typedef A Leaf_t;
  inline static
  Leaf_t make( A &&a) { 
    return a;
  }
};

現在、このバージョンはA戻り時に のコピー コンストラクターを呼び出します。returnステートメントを次のように変更すると

Leaf_t make( A &&a) { 
  return std::move(a);
}

次に、 の移動コンストラクターAが呼び出され、そこでいくつかの最適化を行うことができます。

質問と 100% 一致していない可能性があります。しかし、それreturn std::move(..)が決して必要ではないと考えるのは誤りです。そう思ったこともある。もう違います ;-)

于 2012-11-05T13:33:33.963 に答える