Когда перегрузка проходит по ссылке (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); 

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

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

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

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

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

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

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

  2. Вы знаете, что вам всегда понадобится оператор присваивания копий, чтобы обеспечить надежную гарантию безопасности.

Если вы не уверены в 2, другими словами, вы считаете, что оператору присваивания копии иногда может понадобиться сильная гарантия безопасности исключений, не реализуйте назначение с точки зрения обмена. Клиентам легко достичь такой же гарантии, если вы предоставите один из следующих:

  1. Отсутствие обмена.
  2. Оператор присваивания без вывода.

Например:

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

или:

 template  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 где:

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

Если бы мы имели только:

 void push_back(value_type val); 

Тогда push_back ing lvalue big_legacy_type в vector потребует 2 копии вместо 1, даже если capacity была достаточной. Это было бы катастрофой, результативность была бы разумной.

Обновить

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

 #include  #include  #include  #include  class X { std::vector 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 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 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 случайного размера vector 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.

Вышеуказанный тест, в среднем, имеет достаточную емкость в течение половины времени. Если мы примем это в крайнем случае:

  1. Lhs имеет достаточную емкость все время.
  2. lhs имеет достаточную пропускную способность в любое время.

то преимущество в производительности присвоения копии по умолчанию над идиомой copy / swap варьируется от примерно 560% до 0%. Идиома copy / swap никогда не бывает быстрее и может быть значительно медленнее (для этого теста).

Хотите скорость? Мера.

  • c ++ проблема с перегрузкой функции в унаследованном classе
  • Функции с аргументами const и перегрузкой
  • Правила перегрузки Java
  • как работает перегрузка функций const и non-const?
  • перегрузка constexpr
  • Перегрузка функции C ++ в соответствии с возвращаемым значением
  • Можете ли вы перегрузить методы controllerа в ASP.NET MVC?
  • В чем разница между перегрузкой и переопределением метода?
  • Является ли перегрузка метода рассмотренным polymorphismом?
  • Поддерживает ли EL перегруженные методы?
  • Сложное разрешение перегрузки с одновременным (истинным)
  • Interesting Posts
    Давайте будем гением компьютера.