Когда я должен объявить свою функцию как:
void foo(Widget w);
в отличие от
void foo(Widget&& w);
?
Предположим, что это единственная перегрузка (как, например, я выбираю одну или другую, а не обе и другие перегрузки). Нет шаблонов. Предположим, что функция foo
требует владения Widget
(например, const Widget&
не является частью этого обсуждения). Меня не интересует какой-либо ответ вне сферы этих обстоятельств. См. Добавление в конце сообщения, почему эти ограничения являются частью вопроса.
Основное различие, которое могут возникнуть у моих коллег, состоит в том, что ссылочный параметр rvalue заставляет вас быть явно о копиях. Вызывающий отвечает за создание явной копии, а затем передает ее с помощью std::move
, когда вы хотите получить копию. В случае прохождения по значению стоимость копии скрыта:
//If foo is a pass by value function, calling + making a copy:
Widget x{};
foo(x); //Implicit copy
//Not shown: continues to use x locally
//If foo is a pass by rvalue reference function, calling + making a copy:
Widget x{};
//foo(x); //This would be a compiler error
auto copy = x; //Explicit copy
foo(std::move(copy));
//Not shown: continues to use x locally
Кроме этой разницы. Кроме того, чтобы заставить людей быть явным о копировании и изменении того, сколько синтаксического сахара вы получаете при вызове функции, как еще они отличаются? Что они говорят по-разному о интерфейсе? Являются ли они более или менее эффективными друг другом?
Другие вещи, о которых я и мои коллеги уже подумали:
- Параметр ссылочного значения rvalue означает, что вы можете переместить аргумент, но он не дает ему мандат. Возможно, после этого аргумент, который вы передали на сайте вызова, будет в исходном состоянии. Также возможно, что функция будет есть/изменять аргумент, даже не называя конструктор перемещения, но предполагая, что, поскольку это была ссылка на rvalue, вызывающий абонент отказался от контроля. Переходите по значению, если вы перейдете в него, вы должны предположить, что произошел переход; нет выбора.
- Предполагая никаких исключений, вызов конструктора с одним движением исключается с помощью pass by rvalue.
- У компилятора есть лучшая возможность для элиминации копий/ходов с передачей по значению. Может ли кто-нибудь обосновать это требование? Предпочтительно ссылка на gcc.godbolt.org, показывающая оптимизированный сгенерированный код из gcc/clang, а не строка в стандарте. Моя попытка показать это, вероятно, не смогла успешно изолировать поведение: https://godbolt.org/g/4yomtt
Добавление: Почему я так сильно ограничиваю эту проблему?
- Никаких перегрузок - если бы были другие перегрузки, это перешло бы в обсуждение прохода по значению vs набора перегрузок, которые включают в себя ссылку на const и rvalue, после чего набор перегрузок, очевидно, более эффективен и выигрывает. Это хорошо известно, и поэтому не интересно.
- Нет шаблонов - меня не интересует, как перенаправление ссылок вписывается в изображение. Если у вас есть ссылка на пересылку, вы все равно вызываете std:: forward. Цель с ссылкой на пересылку - передать все, как вы их получили. Копии не актуальны, потому что вы просто передаете lvalue. Это хорошо известно, и не интересно.
-
foo
требует права собственности наWidget
(aka noconst Widget&
). Мы не говорим о функциях только для чтения. Если функция была доступна только для чтения или ей не нужно было владеть или продлевать время жизниWidget
, тогда ответ тривиально становитсяconst Widget&
, что опять же хорошо известно и не интересно. Я также ссылаюсь на то, почему мы не хотим говорить о перегрузках.