2017-02-21 12 views
8

Я написал код шаблона, который принимает функтор в качестве аргумента и после некоторой обработки выполняет его. Хотя кто-то может передать эту функцию лямбда, указатель на функцию или даже std::function, но он предназначен в первую очередь для лямбда (не для того, чтобы запрещать другие форматы). Я хочу спросить, как мне взять эту лямбду - по стоимости? по ссылке? или что-то другое.Прохождение лямбда в качестве аргумента - по ссылке или значению?

Пример кода -

#include <iostream> 
#include <functional> 
using namespace std; 

template<typename Functor> 
void f(Functor functor) 
{ 
    functor(); 
} 

void g() 
{ 
    cout << "Calling from Function\n"; 
} 

int main() 
{ 
    int n = 5; 

    f([](){cout << "Calling from Temp Lambda\n";}); 

    f([&](){cout << "Calling from Capturing Temp Lambda\n"; ++n;}); 

    auto l = [](){cout << "Calling from stored Lambda\n";}; 
    f(l); 

    std::function<void()> funcSTD = []() { cout << "Calling from std::Function\n"; }; 
    f(funcSTD); 

    f(g); 
} 

В коде выше, у меня есть выбор сделать это либо из них -

template<typename Functor> 
    void f(Functor functor) 

template<typename Functor> 
    void f(Functor &functor) 

template<typename Functor> 
    void f(Functor &&functor) 

Что бы быть лучше и почему? Существуют ли какие-либо ограничения для любого из них?

+0

ИМХО, перейдите по значению. – NathanOliver

+1

Я всегда видел, что объекты шаблонов functor передаются по значению, но я не знаю почему; хороший вопрос. – YSC

+2

related/dupe: http://stackoverflow.com/questions/8196345/passing-functor-object-by-value-vs-by-reference-c – NathanOliver

ответ

4

В качестве возможного недостатка обратите внимание, что передача копией не может работать, если лямбда не копируется. Если вам это удастся, прохождение с копией просто отлично.
В качестве примера:

#include<memory> 
#include<utility> 

template<typename F> 
void g(F &&f) { 
    std::forward<F>(f)(); 
} 

template<typename F> 
void h(F f) { 
    f(); 
} 

int main() { 
    auto lambda = [foo=std::make_unique<int>()](){}; 

    g(lambda); 
    //h(lambda); 
} 

В приведенном выше фрагменте, lambda не копируемый из-за foo. Его конструктор копирования удаляется из-за того, что конструктор копирования std::unique_ptr удален.
С другой стороны, F &&f принимает как значения lvalue, так и rvalue, являющиеся ссылкой на отправку, а также ссылки на const.
В других терминах, если вы хотите использовать повторное использование той же лямбда как аргумент более одного раза, вы не можете, если ваши функции получат ваш объект путем копирования, и вы должны перенести его, поскольку он не копируется (ну, на самом деле, вы можете это сделать дело обертывания его в лямбда, которая захватывает внешний по ссылке).

+0

Но 'h (std :: move (лямбда));' будет Работа. – NathanOliver

+2

@NathanOliver Конечно, но вы больше не можете повторно использовать лямбда. Если вы хотите передать его двум функциям, как в примере, это невозможно. – skypjack

+0

@skypjack followup ques - возможно ли отменить подвижный функтор в сценарии, подобном этому, кроме определения двух подобных перегрузок. –

3

Поскольку лямбда-выражения могут иметь свои собственные поля (например, классы), копирование/использование ссылок может привести к разным результатам. Вот простой пример:

template<class func_t> 
size_t call_copy(func_t func) { 
    return func(1); 
} 

template<class func_t> 
size_t call_ref(func_t& func) { 
    return func(1); 
} 

int main() { 
    auto lambda = [a = size_t{0u}] (int val) mutable { 
     return (a += val); 
    }; 
    lambda(5); 

    // call_ref(lambda); – uncomment to change result from 5 to 6 
    call_copy(lambda); 

    std::cout << lambda(0) << std::endl; 

    return 0; 
} 

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

Также - если вы хотите передать lambda в качестве параметра (а не переменную, содержащую его), вы должны использовать ссылку пересылки, чтобы она могла работать.

Смежные вопросы