web-dev-qa-db-ja.com

pass-by-value-and-then-move構造は悪いイディオムですか?

C++には移動セマンティクスがあるため、現在では通常

void set_a(A a) { _a = std::move(a); }

理由は、aが右辺値である場合、コピーは省略され、1回だけ移動するからです。

しかし、aが左辺値である場合はどうなりますか?コピー構成があり、次に移動割り当てがあるようです(Aに適切な移動割り当て演算子があると仮定します)。オブジェクトのメンバー変数が多すぎる場合、移動の割り当てにコストがかかる可能性があります。

一方、私たちがやったら

void set_a(const A& a) { _a = a; }

コピーの割り当ては1つだけです。左辺値を渡す場合、値渡しのイディオムよりもこの方法が好ましいと言えますか?

59
jbgs

高価な移動型は、現代のC++の使用法ではまれです。移動のコストが心配な場合は、両方のオーバーロードを記述します。

_void set_a(const A& a) { _a = a; }
void set_a(A&& a) { _a = std::move(a); }
_

または完全転送セッター:

_template <typename T>
void set_a(T&& a) { _a = std::forward<T>(a); }
_

これは、余分なコピーや移動を必要とせずに、暗黙的に暗黙的にdecltype(_a)に変換可能な左辺値、右辺値、およびその他のものを受け入れます。

左辺値から設定するときに余分な移動が必要ですが、イディオムはbadではありません1行のコードでほぼ最適なパフォーマンス。

43
Casey

一般的な場合値が保存される、値渡しのみが良い妥協です-

左辺値のみが渡されることがわかっている場合(一部の密結合コード)、それは不合理で、賢くない。

両方を提供することによって速度の改善が疑われる場合、最初に2回考え、それが助けにならなければ、測定します。

値が保存されない場合は、参照によるパスを使用します。これは、無数の不必要なコピー操作を防ぐためです。

最後に、プログラミングを考えられないルールの適用に減らすことができれば、ロボットに任せることができます。だから私見では、ルールにあまり注意を向けるのは得策ではありません。さまざまな状況で、利点とコストが何であるかに焦点を当てるのが良いでしょう。コストには、速度だけでなく、たとえばコードサイズと明瞭さ。通常、ルールはこのような利益相反を処理できません。

値を渡し、移動することは、実際には移動可能なことがわかっているオブジェクトのイディオムです。

前述したように、右辺値が渡されると、コピーが削除されるか、移動され、コンストラクタ内で移動されます。

コピーコンストラクターをオーバーロードし、コンストラクターを明示的に移動できますが、複数のパラメーターがある場合はより複雑になります。

例を考えてみましょう、

class Obj {
  public:

  Obj(std::vector<int> x, std::vector<int> y)
      : X(std::move(x)), Y(std::move(y)) {}

  private:

  /* Our internal data. */
  std::vector<int> X, Y;

};  // Obj

明示的なバージョンを提供したい場合、次のような4つのコンストラクターになります。

class Obj {
  public:

  Obj(std::vector<int> &&x, std::vector<int> &&y)
      : X(std::move(x)), Y(std::move(y)) {}

  Obj(std::vector<int> &&x, const std::vector<int> &y)
      : X(std::move(x)), Y(y) {}

  Obj(const std::vector<int> &x, std::vector<int> &&y)
      : X(x), Y(std::move(y)) {}

  Obj(const std::vector<int> &x, const std::vector<int> &y)
      : X(x), Y(y) {}

  private:

  /* Our internal data. */
  std::vector<int> X, Y;

};  // Obj

ご覧のとおり、パラメーターの数を増やすと、必要なコンストラクターの数が順列で増加します。

具象型がなく、テンプレート化されたコンストラクターがある場合、次のように完全転送を使用できます。

class Obj {
  public:

  template <typename T, typename U>
  Obj(T &&x, U &&y)
      : X(std::forward<T>(x)), Y(std::forward<U>(y)) {}

  private:

  std::vector<int> X, Y;

};   // Obj

参照:

  1. 速度を求めますか?値渡し
  2. C++調味料
1
mpark

答えのいくつかを要約しようとするので、私は自分自身に答えています。それぞれの場合、いくつのムーブ/コピーがありますか?

(A)値で渡し、代入構造を移動し、Xパラメーターを渡します。Xが...の場合

一時的:1移動(コピーは省略されます)

左辺値:1コピー1移動

std :: move(lvalue):2移動

(B)参照渡しおよびコピー割り当て通常(C++ 11以前)の構成。Xが...の場合

一時的:1部

左辺値:1コピー

std :: move(lvalue):1コピー

3種類のパラメーターが同じ確率であると仮定できます。したがって、3回の呼び出しごとに、(A)4回の移動と1回のコピー、または(B)3回のコピーがあります。つまり、平均して、(A)コールごとに1.33移動して0.33コピー、または(B)コールごとに1コピーです。

クラスの大部分がPODで構成されている場合、移動はコピーと同じくらい高価です。したがって、ケース(A)ではセッターへの呼び出しごとに1.66コピー(または移動)があり、ケース(B)では1コピーがあります。

状況によっては(PODベースのタイプ)、value-by-then-then-moveコンストラクトは非常に悪い考えであると言えます。 66%遅くなり、C++ 11機能に依存します。

一方、クラスに(動的メモリを使用する)コンテナが含まれる場合、(A)ははるかに高速になります(ほとんど左辺値を渡す場合を除く)。

間違っている場合は修正してください。

1
jbgs