Как правило, рекомендуется использовать лучшие практики 1 до использовать проход по константу ref для всех типов , кроме встроенных типов (char
, int
, double
и т. Д.), Для итераторов и функциональных объектов (лямбда-выражения, классы, производные от std::*_function
).
Это было особенно верно до существования семантики перемещения . Причина проста: если вы передали по значению, необходимо было сделать копию объекта, и, за исключением очень маленьких объектов, это всегда дороже, чем передача ссылки.
С C ++ 11 мы получили семантику перемещения . Вкратце, семантика перемещения позволяет в некоторых случаях передавать объект «по значению», не копируя его. В частности, это тот случай, когда передаваемый объект имеет значение rvalue .
Само по себе перемещение объекта по крайней мере так же дорого, как и передача по ссылке. Тем не менее, во многих случаях функция все равно будет внутренне копировать объект - т.е. она будет иметь владение аргумента. 2
В этих ситуациях мы имеем следующий (упрощенный) компромисс:
- Мы можем передать объект по ссылке, а затем скопировать его внутрь.
- Мы можем передать объект по значению.
«Передача по значению» все еще вызывает копирование объекта, если только объект не является значением. В случае значения r, объект можно вместо этого переместить, так что второй случай внезапно перестает быть «копировать, затем перемещать», а «перемещать, а затем (потенциально) перемещать снова».
Для больших объектов, которые реализуют правильные конструкторы перемещения (такие как векторы, строки ...), второй случай тогда будет значительно более эффективным, чем первый. Поэтому рекомендуется использовать передачу по значению, если функция получает владение аргументом и если тип объекта поддерживает эффективное перемещение .
Историческая справка:
Фактически, любой современный компилятор должен быть в состоянии выяснить, когда передача по значению стоит дорого, и неявно преобразовать вызов, чтобы использовать const ref, если это возможно.
В теории. На практике компиляторы не всегда могут изменить это, не нарушая двоичный интерфейс функции. В некоторых особых случаях (когда функция встроена) копия будет фактически удалена, если компилятор сможет выяснить, что исходный объект не будет изменен с помощью действий в функции.
Но в целом компилятор не может определить это, и появление семантики перемещения в C ++ сделало эту оптимизацию гораздо менее актуальной.
1 Например. в Скотт Мейерс, Эффективный C ++ .
2 Это особенно часто верно для конструкторов объектов, которые могут принимать аргументы и сохранять их внутри, чтобы быть частью состояния построенного объекта.