std :: function - значение и ссылочный аргумент - PullRequest
2 голосов
/ 07 мая 2020

Существуют ли какие-либо практические различия между std::function для типа с параметром значения и с константной ссылкой на параметр значения? Рассмотрим следующий код:

auto foo = [] (VeryBigType i) {     
};

auto bar = [] (const VeryBigType& i) {
};

std::function<void(VeryBigType)> a;
a = foo;
a = bar;

std::function<void(const VeryBigType&)> b;
b = foo;
b = bar;

Этот код компилируется без проблем и отлично работает. Я знаю, что передача по значению vs по ссылке имеет различия в производительности, поэтому foo и bar будут вести себя по-разному. Но есть ли различия в зависимости от типа шаблона std::function? Например, есть ли какие-либо различия в реализации и / или поведении и / или производительности между std::function<void(VeryBigType)>(bar) и std::function<void(const VeryBigType&)>(bar) или эти конструкции эквивалентны?

Ответы [ 2 ]

3 голосов
/ 07 мая 2020

cppreference говорит , что std::function<R(Args...)>::operator() имеет подпись

R operator()(Args... args) const;

и что он вызывает сохраненный вызываемый f в основном f(std::forward<Args>(args)...). Характеристики производительности зависят как от аргумента шаблона, так и от типа аргумента лямбда, и я думаю, было бы полезно просто увидеть все , что может случиться. В вашем случае у вас есть 2 std::function типов, 2 вызываемых объекта и 3 возможные категории значений для аргумента, что дает вам 12 возможностей.

  • std::function<void(VeryBigType)> f = [](VeryBigType i) { }

    • Если вы вызовете это с lvalue, например

      VeryBigType v;
      f(v);
      

      , это скопирует v в аргумент operator(), а затем operator() передаст rvalue лямбда , который переместит значение в i. Общая стоимость: 1 копия + 1 ход

    • Если вы вызываете это с помощью prvalue, например

      f(VeryBigType{});
      

      , тогда это материализует prvalue в аргумент operator(), затем передайте значение r лямбде, которое переместит его в i. Общая стоимость: 1 ход

    • Если вы вызовете это с xvalue, например

      VeryBigType v;
      f(std::move(v));
      

      Это переместит v в аргумент operator(), что передаст rvalue лямбде, которая снова переместит его в i. Общая стоимость: 2 хода.

  • std::function<void(VeryBigType)> f = [](VeryBigType const &i) { }

    • Если вы вызовете это с lvalue, это будет скопировано один раз в аргумент operator(), а затем лямбда будет дана ссылка на этот аргумент. Общая стоимость: 1 копия.

    • Если вы вызовете это с помощью prvalue, это материализует его в аргумент operator(), который передаст ссылку на этот аргумент лямбде. Общая стоимость: ничего.

    • Если вы вызываете это с xvalue, это переместит его в аргумент operator(), который передаст ссылку на этот аргумент лямбде. Общая стоимость: 1 ход.

  • std::function<void(VeryBigType const&)> f = [](VeryBigType i) { }

    • Если вы вызываете это с lvalue или xvalue (т. Е. С glvalue ), operator() получит ссылку на него. Если вы вызовете это с prvalue, оно будет материализовано во временное, и operator() получит ссылку на это. В любом случае внутренний вызов лямбды всегда будет копироваться. Общая стоимость: 1 копия.
  • std::function<void(VeryBigType const&)> f = [](VeryBigType const &i) { }

    • Опять же, независимо от того, как вы это называете, operator() получит только ссылка на него, и лямбда просто получит ту же ссылку. Общая стоимость: ничего.

Итак, что мы узнали? Если и std::function, и лямбда принимают ссылки, вы избегаете посторонних копий и перемещений. По возможности используйте это. Однако размещение лямбды по значению внутри ссылки by- const -lvalue-reference std::function - плохая идея (если только у вас нет to). По сути, ссылка lvalue «забывает» категорию значения аргумента, и аргумент лямбда всегда копируется. Помещение лямбда-выражения by- const -lvalue-reference внутри побочного значения std::function довольно хорошо с точки зрения производительности, но вам нужно это делать только в том случае, если вы вызываете другой код, который ожидает побочное значение std::function, потому что в противном случае по ссылке std::function достигается то же самое, но с меньшим количеством копий и перемещений. Помещение лямбды по значению внутри по значению std::function немного хуже, чем размещение лямбды по значению const -lvalue-reference внутри него из-за дополнительного перемещения во всех вызовах. Было бы лучше вместо этого взять аргумент лямбда по-rvalue-ссылке, что почти то же самое, что взять его по- const -lvalue-reference, за исключением того, что вы все еще можете изменить аргумент, как если бы вы его взяли по значению в любом случае.

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

0 голосов
/ 07 мая 2020

Ваш вопрос сбивает с толку, потому что вы, кажется, знаете, что существует очень большая разница в производительности между значением и аргументом ref.

То, что вы, кажется, не знаете, так это то, что тип шаблона объекта function определяет, как аргументы передаются вашей лямбда-функции, а не сама лямбда-функция, из-за вызова cdecl соглашение: вызывающий передает аргументы в стек, а затем выполняет очистку, а вызывающий вызывает через ваш std::function объект.

Итак, a всегда будет выделять новую копию вашего объекта и передавать ссылку к нему, затем очистите его, и b всегда будет передавать ссылку на исходный объект.

Изменить: почему это работает независимо от того, как вы определяете лямбда-функции, опять же из-за cdecl , обе функции ожидают указатель в качестве первого аргумента и выполняют свою работу с ними. Остальные объявления вокруг типов (размеры типов, константность, ссылки и т. Д. c) используются только для проверки кода внутри функции и проверки того, что сама функция может быть вызвана вашим объектом function (ie, что function отправит указатель в качестве первого аргумента).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...