Когда перегрузка pass by reference (l-value и r-value) предпочтительна для передачи по значению?

Я видел, как он сказал, что operator= написанный, чтобы взять параметр того же типа по значению, служит как оператором присваивания копирования, так и оператором присваивания перемещения в C++11:

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

где альтернативой будет более чем в два раза больше строк с большим количеством повторений кода и возможностью ошибки:

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, например, который определяется как две перегрузки:

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

следуя первому примеру, где pass by value служит в качестве назначения копирования оператор и оператор назначения перемещения, не push_back быть определены в стандарт должен быть одной функцией?

void push_back (value_type val);

1 ответов


для типов, оператор присваивания копий которых может повторно использовать ресурсы, замена с копией почти никогда не является лучшим способом реализации оператора присваивания копий. Например, посмотрите на std::vector:

этот класс управляет буфером динамического размера и поддерживает оба capacity (максимальная длина буфера может содержать), и size (текущая длина). Если vector реализован оператор назначения копирования swap, то независимо от того, что, новый буфер всегда выделяется, если rhs.size() != 0.

, если lhs.capacity() >= rhs.size(), никакой новый буфер не нужно размещать на всех. Можно просто назначить/построить элементы из rhs to lhs. Когда тип элемента тривиально копируется, это может сводиться только к memcpy. Это может быть много много быстрее, чем выделение и освобождение буфера.

та же проблема для std::string.

та же проблема для MyType, когда MyType имеет члены данных, которые являются std::vector и/или std::string.

есть только 2 раза, когда вы хотите рассмотреть возможность реализации назначения копирования с помощью swap:

  1. вы знаете, что swap метод (включая обязательную конструкцию копирования, когда rhs является lvalue) не будет ужасно неэффективным.

  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;
}

теперь есть будут некоторые типы, где реализация назначения копирования с помощью swap будет иметь смысл. Однако эти типы будут исключением, а не правилом.

On:

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_backing an lvalue big_legacy_type на vector потребуется 2 копии вместо 1, даже если capacity было достаточно. Это было бы катастрофой. мудрый.

обновление

вот HelloWorld, который вы должны иметь возможность запускать на любой соответствующей платформе C++11:

#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оператор присваивания копии двумя способами:

  1. неявно, что эквивалентно вызову vectorоператор присвоения копии.
  2. с идиомой copy / swap, наводяще под макросом SLOW_DOWN. Я думал о том, чтобы назвать его SLEEP_FOR_AWHILE, но этот путь на самом деле гораздо хуже, чем заявления сна, Если вы находитесь на устройстве с питанием от батареи.

тест строит некоторые произвольного размера vector<int>s между 0 и 1000, и назначает их миллион раз. Он умножает каждый, суммирует время, а затем находит среднее время в наносекундах с плавающей запятой и печатает это. Если два последовательных вызова ваших часов с высоким разрешением не возвращают что-то менее 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%. Идиома копирования / замены никогда не бывает быстрее и может быть значительно медленнее (для этого теста).

Нужна Скорость? Мера.