Когда аргумент передается шаблону функции, определяющему тип аргумента, возникает интересный вопрос о том, как следует передавать аргумент. Стоит отметить, что точный характер использования аргумента не имеет значения: используется ли аргумент как объект функции, итератор, значение и т. д., совершенно не имеет значения для ответа. Есть четыре варианта:
template <typename T> void f(T&& a)
template <typename T> void f(T& a)
template <typename T> void f(T const& a)
template <typename T> void f(T a)
Немного важно, что на самом деле делает шаблон функции: если все, что f()
делает со своим аргументом a
, — это перенаправить его в другую функцию, вы хотите передать по универсальной ссылке. Вызываемая функция отсортирует детали и отклонит неподходящие варианты. Конечно, хотя в целом функции переадресации и полезны, они несколько скучны.
Если f()
действительно что-то делает с объектом, обычно мы можем сразу отбросить два варианта:
- Передача по универсальной ссылке приводит к типу, о котором мы не знаем, является ли он ссылкой или значением. Это довольно бесполезно для чего-либо, кроме пересылки типа, поскольку он ведет себя либо как значение, либо как ссылка. Просто указать, что на самом деле делает функция, было бы проблематичным танцем.
- Pass by
T const&
тоже не приносит нам много пользы: мы получили ссылку на объект, время жизни которого мы не можем контролировать и из которого мы не можем ни перемещаться, ни копировать/перемещать его.
Передача объекта по ссылке, отличной от const
, может быть полезна, если изменяется сам объект. Очевидно, что это важная часть контракта функции и навязанный выбор дизайна, который не может быть отменен клиентом функции при его принятии.
Предпочтительный подход — принимать аргументы по значению. Как правило, это значительно упрощает определение поведения функции и фактически оставляет за пользователем выбор, должен ли тип следовать за значением или за ссылочной семантикой! Тот факт, что что-то передается по значению, не означает, что интересующие объекты также передаются по значению. Специально для случая функционального объекта стандартная библиотека C++ даже предоставляет универсальный адаптер, предоставляющий семантику ссылки на тип значения: std::ref()
.
Конечно, дизайн интерфейса тонок, и будут случаи, когда каждый из вариантов оправдан. Однако, как правило, я думаю, что это очень просто:
- Функции пересылки используют универсальные ссылки.
- Функции, которые что-то делают с аргументами, используют значения.
... и, конечно же, эти правила применяются только к аргументам шаблонов функций, тип которых выводится.
person
Dietmar Kühl
schedule
20.11.2012