2015-07-07 7 views
8

Normalerweise verwende ich das folgende Muster, wenn eine Lambda als Argument für eine Funktion (A Template-Klasse übergeben-by-Wert) akzeptieren:Sollte ich ein Lambda durch const Referenz übergeben.

template <class Function> 
void higherOrderFunction(Function f) { 
    f(); 
} 

Ist diese Kopie (die Schließung) das Argument? Wenn ja, ist etwas falsch mit der Annahme der Lambda durch const Referenz stattdessen?

template <class Function> 
void higherOrderFunction(const Function& f) { 
    f(); 
} 

Ein einfacher Test scheint darauf hinzudeuten, dass dies funktioniert gut, aber ich möchte wissen, ob es spezielle Überlegungen, die ich bewusst sein sollten.

+0

Ich glaube, das kopiert nicht, aber bewegen. Sollte mit einer anständigen Implementierung schnell genug sein. Lambdas sind so konzipiert, dass sie von einem solchen Wert weitergegeben werden - alle Standardalgorithmen nehmen ihre Funktoren wert. –

Antwort

10

Wenn Sie nach Wert übergeben, kopieren Sie das Closure-Objekt (vorausgesetzt, Sie definieren das Lambda nicht inline, in diesem Fall wird es verschoben). Dies kann unerwünscht sein, wenn der Zustand teuer zu kopieren ist, und wird nicht kompiliert, wenn der Zustand nicht kopierbar ist.

template <class Function> 
void higherOrderFunction(Function f); 

std::unique_ptr<int> p; 
auto l = [p = std::move(p)] {}; // C++14 lambda with init capture 
higherOrderFunction(l);   // doesn't compile because l is non-copyable 
           // due to unique_ptr member 
higherOrderFunction([p = std::move(p)] {}); // this still works, the closure object is moved 

Wenn Sie mit dem const Verweis übergeben, dann können Sie keine mutable Lambda übergeben, die ihre Datenelemente als Argument für higherOrderFunction() ändert, weil ein mutable Lambda eine nicht constoperator() hat, und Sie können nicht, dass const auf eine aufrufen Objekt.

template <class Function> 
void higherOrderFunction(Function const& f); 

int i = 0; 
higherOrderFunction([=]() mutable { i = 0; }); // will not compile 

Die beste Option ist die Verwendung einer Weiterleitungsreferenz. Dann kann higherOrderFunction entweder lvalues ​​oder rvalues ​​akzeptieren, die der Anrufer übergibt.

template <class Function> 
void higherOrderFunction(Function&& f) { 
    std::forward<Function>(f)(); 
} 

Dies ermöglicht die einfache Fälle sowie die oben genannten zu kompilieren. Für eine Diskussion, warum std::forward verwendet werden sollte, siehe this answer.

Live demo

+0

"Wenn Sie nach Wert übergeben, werden Sie das Closure-Objekt kopieren." Dies ist nicht unbedingt richtig und im häufigsten Fall sogar falsch. Bei Wert übergeben wird eine Verschiebung für einen R-Wert, nicht für eine Kopie, und der häufigste Fall für Lambdas ist das Erstellen des Lambda-Inline-Werts, das zu einem R-Wert führen sollte. Ihre Antwort führt auch zu einer sofortigen Frage: Wenn Forwarding-Referenzen am besten sind, warum verwendet die STL Wert statt Weiterleitung? (ehrliche Frage) –

+1

@Nir Einverstanden, aktualisiert die Antwort, um das klar zu machen. Was STL-Algorithmen angeht, die das Prädikat wertmäßig verwenden, vermute ich, dass die Weiterleitungsreferenzen nicht da waren. Jedenfalls kann ich mir kein Argument vorstellen, in diesem Fall keine Weiterleitungsreferenz zu verwenden. – Praetorian

+3

Um fair zu sein, mit 'std' Algorithmen, die Funktionen nach Wert nehmen, sollten die Leute wissen, dass sie, wenn sie ein kostenintensives Funktionsobjekt mit einer begrenzten Lebenszeit haben,' std :: ref' es sollten. – Yakk

1

Eine Kopie ist eine Kopie, so dass Sie nicht das Original mutieren kann, könnten einige Auswirkungen auf die Leistung haben, wenn eine Menge von Daten beteiligt ist:

#include <iostream> 
using namespace std; 

template<typename Fn> 
void call_value(Fn f) { f(); } 
template<typename Fn> 
void call_ref(Fn & f) { f(); } 
template<typename Fn> 
void call_cref(Fn const & f) { f(); } 

struct Data { 
    Data() {} 
    Data(Data const &) { 
    cout << "copy" << endl; 
    } 
    Data(Data &&) { 
    cout << "move" << endl; 
    } 
}; 

int main(int, char **) { 
    Data data; 

    auto capref = [&data]() {}; 
    cout << "capture by value, so we get a "; 
    auto capcp = [data]() {}; 

    cout << " the lambda with a reference ... "; 
    call_value(capref); 
    cout << " could now be called and mutate .. "; 
    call_ref(capref); 
    call_cref(capref); 
    cout << " but won't, as it had to be declared mutable " << endl; 

    cout << "the lambda with an instance: "; 
    call_value(capcp); 
    cout << "but not "; 
    call_ref(capcp); 
    call_cref(capcp); 
    cout << " the reference versions " << endl; 

    bool en = false; 
    auto trigger = [en](bool enable = true) mutable { 
    if (en) { 
     cout << "fire!" << endl; 
    } 
    if (en or enable) { 
     en = true; 
    } 
    }; 
    cout << "won't shoot" << endl; 
    trigger(false); 
    call_value(trigger); 
    trigger(false); 
    call_ref(trigger); 
    cout << "and now ... "; 
    trigger(false); 
    // const ref won't work 
    return 0; 
} 

anzeigen in action.

Nicht vergessen: Lambdas sind nur syntaktischer Zucker für aufrufbare Klassen. (Aber sehr hilfreich)

Verwandte Themen