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