29

operator=C++ 11 では、同じ型のパラメーターを値で受け取るように書かれたものが、コピー代入演算子と移動代入演算子の両方として機能すると言われています。

Foo& operator=(Foo f)
{
    swap(f);
    return *this;
}

代替案は、コードの繰り返しが多く、エラーの可能性がある 2 倍以上の行になります。

Foo& operator=(const Foo& f)
{
    Foo f2(f);
    swap(f2);
    return *this;
}

Foo& operator=(Foo&& f)
{
    Foo f2(std::move(f));
    swap(f2);
    return *this;
}

ref-to-const と r-value のオーバーロードが値渡しに適しているのはどのような状況ですか?それが必要になるのはいつですか? std::vector::push_backたとえば、これは 2 つのオーバーロードとして定義されています。

void push_back (const value_type& val);
void push_back (value_type&& val);

値による受け渡しがコピー代入演算子および移動代入演算子として機能push_backする最初の例に従って、標準で単一の関数として定義できませんでしたか?

void push_back (value_type val);
4

1 に答える 1

35

コピー代入演算子がリソースをリサイクルできる型の場合、コピーとの交換がコピー代入演算子を実装する最良の方法になることはほとんどありません。たとえば、次を見てくださいstd::vector

このクラスは、動的にサイズ変更されたバッファーを管理し、a capacity(バッファーが保持できる最大長) と a size(現在の長さ) の両方を維持します。vectorコピー代入演算子が実装されている場合、swap何があっても新しいバッファが常に割り当てられrhs.size() != 0ます。

ただし、 の場合lhs.capacity() >= rhs.size()、新しいバッファをまったく割り当てる必要はありません。rhsからまでの要素を単純に割り当て/構築できますlhs。要素の型が自明にコピー可能な場合、これはmemcpy. これは、バッファの割り当てと割り当て解除よりもはるかに高速です。

の同じ問題std::string

および/またはであるデータ メンバーがあるMyType場合の同じ問題。MyTypestd::vectorstd::string

スワップを使用したコピー割り当ての実装を検討する必要があるのは 2 回だけです。

  1. このswap方法 (rhs が左辺値である場合の必須のコピー作成を含む) は、それほど非効率的ではないことがわかっています。

  2. 強力な例外安全性を保証するには、コピー代入演算子が常に必要になることがわかっています。

2 について確信が持てない場合、つまり、コピー代入演算子が強力な例外安全性の保証を必要とする場合があると思われる場合は、スワップに関して代入を実装しないでください。次のいずれかを提供すれば、クライアントは同じ保証を簡単に達成できます。

  1. noexcept スワップ。
  2. noexcept 移動代入演算子。

例えば:

template <class T>
T&
strong_assign(T& x, T y)
{
    using std::swap;
    swap(x, y);
    return x;
}

また:

template <class T>
T&
strong_assign(T& x, T y)
{
    x = std::move(y);
    return x;
}

現在、スワップを使用してコピー割り当てを実装することが理にかなっているタイプがいくつかあります。ただし、これらのタイプは例外であり、規則ではありません。

の上:

void push_back(const value_type& val);
void push_back(value_type&& val);

どこを想像してくださいvector<big_legacy_type>

class big_legacy_type
{
 public:
      big_legacy_type(const big_legacy_type&);  // expensive
      // no move members ...
};

もし私たちが持っていたのは:

void push_back(value_type val);

次にpush_back、左辺値big_legacy_typeを a にing すると、十分なvector場合でも、1 つではなく 2 つのコピーが必要になります。capacityそれは、パフォーマンスに関しては惨事になるでしょう。

アップデート

これは、C++11 準拠のプラットフォームで実行できる HelloWorld です。

#include <vector>
#include <random>
#include <chrono>
#include <iostream>

class X
{
    std::vector<int> v_;
public:
    explicit X(unsigned s) : v_(s) {}

#if SLOW_DOWN
    X(const X&) = default;
    X(X&&) = default;
    X& operator=(X x)
    {
        v_.swap(x.v_);
        return *this;
    }
#endif
};

std::mt19937_64 eng;
std::uniform_int_distribution<unsigned> size(0, 1000);

std::chrono::high_resolution_clock::duration
test(X& x, const X& y)
{
    auto t0 = std::chrono::high_resolution_clock::now();
    x = y;
    auto t1 = std::chrono::high_resolution_clock::now();
    return t1-t0;
}

int
main()
{
    const int N = 1000000;
    typedef std::chrono::duration<double, std::nano> nano;
    nano ns(0);
    for (int i = 0; i < N; ++i)
    {
        X x1(size(eng));
        X x2(size(eng));
        ns += test(x1, x2);
    }
    ns /= N;
    std::cout << ns.count() << "ns\n";
}

Xのコピー代入演算子を次の 2 つの方法でコーディングしました。

  1. 暗黙的に、これはvectorのコピー代入演算子を呼び出すことと同じです。
  2. copy/swap イディオムを使用して、暗示的にマクロの下にSLOW_DOWN. という名前を付けることを考えSLEEP_FOR_AWHILEましたが、バッテリー駆動のデバイスを使用している場合、この方法は実際にはスリープステートメントよりもはるかに悪いです。

vector<int>このテストでは、0 から 1000 の間のランダムなサイズの s をいくつか作成し、それらを 100 万回割り当てます。それぞれの時間を計測し、時間を合計してから、浮動小数点ナノ秒単位で平均時間を見つけて出力します。高解像度クロックを 2 回連続して呼び出しても 100 ナノ秒未満の値が返されない場合は、ベクトルの長さを大きくすることをお勧めします。

ここに私の結果があります:

$ clang++ -std=c++11 -stdlib=libc++ -O3 test.cpp
$ a.out
428.348ns
$ a.out
438.5ns
$ a.out
431.465ns
$ clang++ -std=c++11 -stdlib=libc++ -O3 -DSLOW_DOWN test.cpp
$ a.out
617.045ns
$ a.out
616.964ns
$ a.out
618.808ns

この簡単なテストでは、コピー/スワップ イディオムで 43% のパフォーマンス ヒットが見られます。YMMV。

上記のテストでは、平均して、lhs の半分の時間で十分な容量があります。これを極端に考えると、次のようになります。

  1. lhs には常に十分な容量があります。
  2. lhs には常に十分な容量があります。

その場合、コピー/スワップ イディオムに対するデフォルトのコピー割り当てのパフォーマンス上の利点は、約 560% から 0% まで変化します。copy/swap イディオムは決して高速ではなく、劇的に遅くなる可能性があります (このテストの場合)。

スピードが欲しいですか?測定。

于 2013-08-18T21:20:27.333 に答える