Хорошая практика передачи функции в качестве параметра: копия, ссылка, ссылка на константу?

Возможный дубликат:
проходит мимо шаблона значение или постоянная ссылка или…?

Какова хорошая практика в следующем для функции, принимающей функцию в качестве параметра:

template<class Function> void test1(Function f);
template<class Function> void test2(Function& f);
template<class Function> void test3(const Function& f);

где переданная функция может быть функтором, std::function, указателем функции или лямбда-функцией.


person Vincent    schedule 20.11.2012    source источник
comment
@MooingDuck: я думаю, что это несколько иной (более общий) вопрос.   -  person NPE    schedule 21.11.2012
comment
@NPE: он более общий, но ответы также отвечают на этот вопрос.   -  person Mooing Duck    schedule 21.11.2012
comment
@MooingDuck: я не согласен с принятым ответом на вопрос, на который вы ссылаетесь! Я объясню в ответ, хотя.   -  person Dietmar Kühl    schedule 21.11.2012
comment
@DietmarKühl: В таком случае, может быть, принятый ответ на другой вопрос требует вашего редактирования? :)   -  person Mooing Duck    schedule 21.11.2012
comment
@MooingDuck: я не думаю, что уместно полностью изменить чей-то ответ.   -  person Dietmar Kühl    schedule 21.11.2012


Ответы (2)


Используйте универсальные ссылки, и вам не нужно будет об этом думать:

template<class Function> void test(Function&& f);
person ecatmur    schedule 20.11.2012

Когда аргумент передается шаблону функции, определяющему тип аргумента, возникает интересный вопрос о том, как следует передавать аргумент. Стоит отметить, что точный характер использования аргумента не имеет значения: используется ли аргумент как объект функции, итератор, значение и т. д., совершенно не имеет значения для ответа. Есть четыре варианта:

  1. template <typename T> void f(T&& a)
  2. template <typename T> void f(T& a)
  3. template <typename T> void f(T const& a)
  4. template <typename T> void f(T a)

Немного важно, что на самом деле делает шаблон функции: если все, что f() делает со своим аргументом a, — это перенаправить его в другую функцию, вы хотите передать по универсальной ссылке. Вызываемая функция отсортирует детали и отклонит неподходящие варианты. Конечно, хотя в целом функции переадресации и полезны, они несколько скучны.

Если f() действительно что-то делает с объектом, обычно мы можем сразу отбросить два варианта:

  1. Передача по универсальной ссылке приводит к типу, о котором мы не знаем, является ли он ссылкой или значением. Это довольно бесполезно для чего-либо, кроме пересылки типа, поскольку он ведет себя либо как значение, либо как ссылка. Просто указать, что на самом деле делает функция, было бы проблематичным танцем.
  2. Pass by T const& тоже не приносит нам много пользы: мы получили ссылку на объект, время жизни которого мы не можем контролировать и из которого мы не можем ни перемещаться, ни копировать/перемещать его.

Передача объекта по ссылке, отличной от const, может быть полезна, если изменяется сам объект. Очевидно, что это важная часть контракта функции и навязанный выбор дизайна, который не может быть отменен клиентом функции при его принятии.

Предпочтительный подход — принимать аргументы по значению. Как правило, это значительно упрощает определение поведения функции и фактически оставляет за пользователем выбор, должен ли тип следовать за значением или за ссылочной семантикой! Тот факт, что что-то передается по значению, не означает, что интересующие объекты также передаются по значению. Специально для случая функционального объекта стандартная библиотека C++ даже предоставляет универсальный адаптер, предоставляющий семантику ссылки на тип значения: std::ref().

Конечно, дизайн интерфейса тонок, и будут случаи, когда каждый из вариантов оправдан. Однако, как правило, я думаю, что это очень просто:

  1. Функции пересылки используют универсальные ссылки.
  2. Функции, которые что-то делают с аргументами, используют значения.

... и, конечно же, эти правила применяются только к аргументам шаблонов функций, тип которых выводится.

person Dietmar Kühl    schedule 20.11.2012
comment
1. Не может быть бесполезным, если T является функтором с operator() перегрузками для & против &&. - person ildjarn; 21.11.2012
comment
@ildjarn: я говорю не об интерфейсе объекта функции, а о том, как выведенный аргумент принимается шаблоном функции. - person Dietmar Kühl; 21.11.2012
comment
Я не понимаю... Я говорю, что T и a() по сравнению с T&& и std::forward<T>(a)() могут вести себя по-разному - уважение к этой разнице потенциалов не кажется мне совершенно "бесполезным". - person ildjarn; 21.11.2012
comment
Обратите внимание, что я также описал поведение переадресации! Да, когда значения пересылаются, вы хотите использовать универсальные ссылки для определения точного типа. - person Dietmar Kühl; 21.11.2012