Должен ли я передать std:: function по const-reference?

Скажем, у меня есть функция, которая принимает std::function:

void callFunction(std::function<void()> x)
{
    x();
}

Должен ли я передать x по const-reference вместо?:

void callFunction(const std::function<void()>& x)
{
    x();
}

Изменяется ли ответ на этот вопрос в зависимости от того, что делает с ним функция? Например, если это функция-член класса или конструктор, который сохраняет или инициализирует std::function в переменной-члене.

Ответ 1

Если вы хотите производительность, перейдите по значению, если вы его сохраняете.

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

std::future<void> run_in_ui_thread( std::function<void()> )

который запускает некоторый код в потоке "ui", затем сигнализирует future по завершении. (Полезно в интерфейсах пользовательского интерфейса, где поток пользовательского интерфейса - это то место, где вы должны взаимодействовать с элементами пользовательского интерфейса)

Мы имеем две сигнатуры, которые мы рассматриваем:

std::future<void> run_in_ui_thread( std::function<void()> ) // (A)
std::future<void> run_in_ui_thread( std::function<void()> const& ) // (B)

Теперь мы можем использовать их следующим образом:

run_in_ui_thread( [=]{
  // code goes here
} ).wait();

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

В случае (A), std::function напрямую строится из нашей лямбда, которая затем используется в run_in_ui_thread. Лямбда move d в std::function, поэтому любое подвижное состояние эффективно переносится в нее.

Во втором случае создается временная std::function, лямбда move d, а затем временная std::function используется ссылкой в ​​run_in_ui_thread.

До сих пор, так хорошо - они оба выполнялись одинаково. За исключением того, что run_in_ui_thread собирается сделать копию своего аргумента функции для отправки в поток ui для выполнения! (он вернется, прежде чем он будет сделан с ним, поэтому он не может просто использовать ссылку на него). Для случая (A) просто move std::function в его долговременное хранение. В случае (B) мы вынуждены скопировать std::function.

Этот магазин делает переход по значению более оптимальным. Если есть возможность сохранить копию std::function, перейдите по значению. В противном случае, в любом случае, это примерно эквивалентно: единственная минус по значению - это то, что вы принимаете тот же громоздкий std::function и используете один подпоточный метод за другим. При этом a move будет таким же эффективным, как const&.

Теперь есть некоторые другие различия между ними, которые в основном срабатывают, если у нас есть постоянное состояние в std::function.

Предположим, что std::function хранит некоторый объект с operator() const, но также имеет некоторые элементы данных mutable, которые он изменяет (как грубо!).

В случае std::function<> const& измененные члены данных mutable будут распространяться вне вызова функции. В случае std::function<> они не будут.

Это относительно странный угловой случай.

Вы хотите лечить std::function, как и любой другой, возможно, тяжелый, дешево-подвижный тип. Перемещение дешево, копирование может быть дорогостоящим.

Ответ 2

Если вас беспокоит производительность, и вы не определяете виртуальную функцию-член, то вам, скорее всего, вообще не следует использовать std::function.

Создание типа функтора параметра шаблона позволяет увеличить оптимизацию, чем std::function, включая вложение логики функтора. Эффект этих оптимизаций, вероятно, значительно переведет проблемы с копией-vs-косвенностью о том, как передать std::function.

Быстрее

template<typename Functor>
void callFunction(Functor&& x)
{
    x();
}

Ответ 3

Как обычно в С++ 11, передача по ссылке value/reference/const-reference зависит от того, что вы делаете с вашим аргументом. std::function не отличается.

Передача по значению позволяет переместить аргумент в переменную (обычно это переменная-член класса):

struct Foo {
    Foo(Object o) : m_o(std::move(o)) {}

    Object m_o;
};

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

Foo f1{Object()};               // move the temporary, followed by a move in the constructor
Foo f2{some_object};            // copy the object, followed by a move in the constructor
Foo f3{std::move(some_object)}; // move the object, followed by a move in the constructor

Я считаю, что вы уже знаете семантику (non) const-ссылок, поэтому я не буду расстраивать эту тему. Если вам нужно, чтобы я добавил больше объяснений об этом, просто спросите, и я обновлю.