Принять параметр «только для перемещения» по значению или ссылке на значение

Принятый ответ этого постаПередача по значению против передачи по значению rvalue Говорит, что:

Для типов только для перемещения (какstd::unique_ptr), передача по значению кажется нормой ...

Я немного сомневаюсь в этом. Допустим, есть некоторый не копируемый тип,Foo, что тоже не дешево для перемещения; и какой-то типBar у которого есть членFoo.

class Foo {
public:
    Foo(const Foo&) = delete;
    Foo(Foo&&) { /* quite some work */ }
    ...
};

class Bar {
public:
    Bar(Foo f) : f_(std::move(f)) {}    // (1)
    Bar(Foo&& f) : f_(std::move(f)) {}  // (2)
    // Assuming only one of (1) and (2) exists at a time

private:
    Foo f_;
};

Тогда для следующего кода:

Foo f;
...
Bar bar(std::move(f));

Конструктор (1) использует 2 конструкции перемещения, а конструктор (2) - только 1. Я также помню, что читал в книге Скотта Мейерса.Эффективный Современный C ++ об этом, но не могу вспомнить, какой элемент сразу.

Поэтому мой вопрос заключается в том, что для типов «только для перемещения» (или, в более общем случае, когда мы хотим передать владение аргументом), не следует ли нам предпочесть pass-by-rvalue-reference для лучшей производительности?

ОБНОВИТЬ: Я знаю, что конструкторы / операторы присваивания по значению (иногда называемыеобъединительный операторы / операторы присваивания) могут помочь устранить дублирующийся код. Я должен сказать, что меня больше интересует случай, когда (1) важна производительность, и (2) тип не подлежит копированию, поэтому нет дублирующих операторов / операторов присваивания, которые принимаютconst lvalue ссылочные аргументы.

ОБНОВЛЕНИЕ 2: Итак, я нашел блог Скотта Мейерса о конкретной проблеме:http://scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html, Этот блог обсуждает причину, которую он защищает в пункте 41 егоЭффективный Современный C ++ тот:

Рассмотрим передачу по значению только длякопируемые параметры...которыедешево двигаться...[а также]всегда копируется.

В этом пункте широко обсуждается передача по значению в сравнении со ссылкой на rvalue, слишком много, чтобы цитировать здесь. Дело в том, что оба способа имеют свои преимущества и недостатки, но для передачи права собственности на объект, предназначенный только для перемещения, передача по значению rvalue представляется предпочтительной.

 M.M03 июл. 2016 г., 11:00
@ZizhengTai Я предпочитаю использовать по умолчанию самый простой для чтения и наименее вероятно содержащий ошибки; оптимизация может прийти позже
 Zizheng Tai03 июл. 2016 г., 11:05
@ М.М: Хорошо, да ... но действительно ли добавление двух амперсандов вредит читабельности? Я предпочел бы утверждать, что это добавляет больше несоответствия. Почему мы принимаем параметры to-be-move-from по значению, в то время как наши конструкторы перемещения принимают ссылки на rvalue?
 Holt01 июл. 2016 г., 10:33
Это просто зависит от контекста и т. Д. Например, используя передачу по значению, избегайте использования тонны дублирующих конструкторов в некоторых случаях. Также,«[...] Foo, который также не дешев, чтобы двигаться;»Конструктор перемещения должен быть (очень) дешев, в противном случае вы не можете установить общие правила относительно того, следует ли вам двигаться или нет.
 Arunmu01 июл. 2016 г., 10:52
pass-by-value как правило,хорошо по умолчанию в этом случае. Это не значит, что это самый эффективный, это просто хорошийдефолт и не должен беспокоить вас с точки зрения производительности. Но если вы сконцентрируетесь на этом конкретном вызове функции во время анализа производительности, то вы, вероятно, можете добавить функцию, принимающую rvalue ref напрямую, и выполнить измерение снова.
 M.M03 июл. 2016 г., 10:36
Обычно не копируемые, но подвижные объекты дешевы для перемещения
 Zizheng Tai03 июл. 2016 г., 10:39
@ М.М. Как правило, да, но всегда нет. Я думаю, что как программисты на C ++ мы должны по умолчанию выбрать наиболее эффективный способ действий, если только для этого нет причин. (И иногда есть, например, гарантия сильного исключения. Но это не является неуправляемым.)
 Slava01 июл. 2016 г., 10:58
для типов только для перемещения вы можете использовать ссылки на r-значения повсюду. Использовать параметры-значения не имеет смысла, только потенциальный недостаток двойного хода. В целом, однако, для копируемых и подвижных типов я бы придерживался значения, когда это имеет смысл.
 Zizheng Tai01 июл. 2016 г., 10:38
@Holt Да, передача по значению действительно полезна для устранения дублирующих конструкторов; Моя главная задача, когда производительность важнее. И иногда перемещение ctors просто не может быть (очень) дешевым. В качестве примера,std::string реализации, которые используютоптимизация небольших строк не может избежать полной копии коротких строк даже в ctor хода.

Ответы на вопрос(2)

Фон

семантика перемещения проистекает именно из необходимости датьбыстро альтернатива копиям, когда семантика позволяет.

Вы приводите примерstd::string и SSO. Однако этот пример явно ошибочен (я сомневаюсь, что они даже включили оптимизацию), потому что копирование 16 байтов черезmemcpy должно занять несколько циклов ЦП, так как он может быть реализован в 1 SIMD-инструкции, чтобы сохранить их все сразу. Кроме того, MSVC 10 действительно старый.

Поэтому мой вопрос заключается в том, что для типов «только для перемещения» (или, в более общем случае, когда мы хотим передать владение аргументом), не следует ли нам предпочесть pass-by-rvalue-reference для лучшей производительности?

Я не буду говорить о производительности, потому что это такой специфический аспект, и его нельзя анализировать «в целом». Нам нужны конкретные случаи. Также необходимо учитывать оптимизацию компилятора; довольно сильно, на самом деле. И не забыть тщательный анализ производительности.

std::unique_ptr немного отличается, потому что (i) может быть перемещено только из-за владения семантикой (ii)является дешево двигаться.

Мой взгляд. Я бы сказал, что если вам нужно предоставить «более быструю» альтернативу в качестве API, предоставьте и то, и другое, какstd::vector::push_back, Это может иметь, как вы говорите, небольшие улучшения.
В противном случае, даже для типов только для перемещения, передача по const-reference все еще работает, и если вы думаете, что это не так, переходите к передаче по значению.

 Zizheng Tai03 июл. 2016 г., 10:45
И даже еслиstd::unique_ptr дешево перемещать, стандарт предпочитает передавать его по ссылке. Посмотрите на один из ctorsstd::shared_ptr: template<class Y, class Deleter> shared_ptr(std::unique_ptr<Y, Deleter>&& r );
 edmz03 июл. 2016 г., 13:06
Я старался как можно дольше избегать ответа, потому что этонет смысл выбирать pass-by-value / rvref исключительно на этом. Это большестиль решение до конкретных примеров. Надеюсь, что это имеет смысл.
 Chris Beck01 июл. 2016 г., 13:09
«Трудно представить класс, который стоит переместить». Один из способов, которым может возникнуть такой класс, - это если класс передает необработанные указатели самому себе многим другим взаимодействующим объектам. Такая конструкция может быть законной, если перемещение происходит редко, а оптимизация перемещений гораздо менее важна, чем оптимизация поисков. В таких случаях перемещение, как правило, должно быть запрещено, или все эти объекты должны быть отслежены и уничтожены, либо дан новый указатель на новый объект. Это также может легко произойти при взаимодействии с библиотеками C или привязками для какого-либо другого языка программирования, где указатели довольно распространены.
 Dan M.26 нояб. 2018 г., 12:52
На самом деле довольно легко представить класс, который был бы дорогим для перемещения (по крайней мере, таким же дорогим, как его копирование).int[4096]/std::array<int, 4096> например.
 Zizheng Tai03 июл. 2016 г., 10:18
scottmeyers.blogspot.com/2014/07/... Так что мое скромное мнение таково, что, как говорит Скотт Мейерс, длятипы только для перемещения кажется, что передача по номеру ссылки является наиболее разумным выбором, по крайней мере, с точки зрения производительности. Если тип параметра является копируемым и перемещаемым, тогда да, передача по значению является хорошим значением по умолчанию, которое можно оставить обнуленным только при необходимости. Но для типов только для перемещения я бы сказал, что передача по значению не имеет особого смысла. За исключением, конечно, того, что некоторые люди думают, что по значению это способ выражения параметра приемника (в то время как я предпочитаю rvalue ref).

на, включенный только для ссылок, похожих на Foo, дает нам идеальную пересылку и единственную реализацию конструктора:

#include <iostream>
#include <utility>

class Foo {
public:
    Foo() {}
    Foo(const Foo&) = delete;
    Foo(Foo&&) { /* quite some work */ }
};

class Bar {
public:
  template<class T, std::enable_if_t<std::is_same<std::decay_t<T>, Foo>::value>* = nullptr>
    Bar(T&& f) : f_(std::forward<T>(f)) {}  // (2)
    // Assuming only one of (1) and (2) exists at a time

private:
    Foo f_;
};

int main()
{
  Foo f;
  Bar bar(std::move(f));

  // this won't compile
//  Foo f2;
//  Bar bar2(f2);

}
 Zizheng Tai01 июл. 2016 г., 11:35
Но в «нормальном» случае (когда мы не используем ctor с совершенной пересылкой), передача объекта подклассаFoo вBars ctor будет вызывать ctor копирования / перемещения. Разве мы не должны имитировать это поведение в ctor с совершенной пересылкой?
 Zizheng Tai01 июл. 2016 г., 11:28
Похожеis_base_of лучше, чемis_same в случае подклассовFoo передаютсяBarКоторы :)
 Richard Hodges01 июл. 2016 г., 11:32
@ZizhengTai осторожно. Все, что можно сделать, это разрешить нарезку. Это, вероятно, последнее, что вы хотите.
 Zizheng Tai01 июл. 2016 г., 11:38
Да, имеет смысл.
 Richard Hodges01 июл. 2016 г., 11:38
@ZizhengTai Если это то, что вы действительно хотите, конечно. Но я думаю, что почти во всех реальных случаях разрезание перемещенных или скопированных объектов - это проклятие, а не выгода.

Ваш ответ на вопрос