2013-05-21 19 views
6

Betrachten Sie das folgende Beispiel stumm:Setter einer Lambda-Funktion?

class MyClass 
{ 
    public: 
     template <class Function> 
     inline double f(double x, Function&& function) 
     { 
      return function(x); 
     } 
}; 

Mit dieser Klasse kann ich MyClass::f(x, function) nennen, mit einer Lambda-Funktion auf x auszuführen, mit (hoffentlich) kein Overhead. Meine Frage ist: Was wäre das Äquivalent mit der function als einstellbares Mitglied der MyClass?

class MyClass 
{ 
    public: 
     inline double f(double x) 
     { 
      return _function(x); 
     } 
    // What are the setter and the type of the protected member _function ? 
}; 
+2

Es könnte alles sein, was ein einziges Doppel und ein Doppel nimmt. Sie könnten 'std :: function ' verwenden. – juanchopanza

Antwort

10

Lambda-Funktionen (sowie einige andere Arten von "aufrufbaren" Funktionen) können mithilfe der Vorlagenklasse std::function, die sich im Header <functional> befindet, eingepackt und gespeichert werden. Sein Template-Parameter ist eine Funktion Signatur mit der Syntax

ReturnType(ArgumentType1, ArgumentType2, ...) 

so in Ihrem Fall die gesamte Funktion Wrapper wird

std::function<double(double)> 

und damit Ihr Code wird

class MyClass 
{ 
    public: 
     inline double f(double x) 
     { 
      return _function(x); 
     } 
     void setFunction(std::function<double(double)> && f) 
     { 
      _function = f; 
     } 
    private: 
     std::function<double(double)> _function; 
}; 

std::function ist " mehr als ein Wrapper für Funktionszeiger. Wie Sie vielleicht wissen, können Lambda-Funktionen einen Teil des Variablenkontexts erfassen, der irgendwo gespeichert werden muss. std::function macht das für Sie transparent.

Beachten Sie, dass std::function überladene Signatur-/Vorlagenanrufoperatoren für Funktoren nicht unterstützt. Bei der Zuweisung eines Funktors mit einer Signatur eines Anrufers wie T operator()(T value) zu einem std::function<double(double)> kann dieser nur mit dieser Signatur aufgerufen werden. So gibt es keine std::function<T(T)> (es sei denn, T ist bereits bekannt, wie ein Template-Parameter Ihrer Klasse).


Eine Alternative, die in einigen Fällen effizienter sein könnten (Sie müssen es Benchmark/Profil), ist Ihre ganze Klasse ein Template-Klasse mit der Funktion Typ Parameter ist die Template-Parameter zu machen.Dann können Sie eine Funktion als Mitglied speichern:

template<typename Function> 
class MyClass 
{ 
    public: 
     MyClass(Function && f) : 
      _function(f) 
     {} 
     inline double f(double x) 
     { 
      return _function(x); 
     } 
    private: 
     Function _function; 
}; 

Um ein solches Objekt zu erstellen, müssen Sie die Template-Parameter angeben, wie folgt aus:

auto myLambda = [](double x){ return x * 0.25; }; 
MyClass<decltype(myLambda)> myObject { myLambda }; 

Um dieses hässliche syntaktischen Kopf zu vermeiden, fügen Sie eine „Hersteller“ -Funktion, die den Vorteil von Template-Typ Abzug nimmt:

template<typename Function> 
auto makeMyClass(Function && f) -> MyClass<Function> { 
    return MyClass<Function>(f); 
} 

Dann wird der Code besser lesbar, mit der Verwendung von auto wieder:

auto myLambda = [](double x){ return x * 0.25; }; 
auto myObject = makeMyClass(myLambda); 
+2

Ich mag Ihre Template-Klassenmethode. Ich werde all dies benchmarken und die Ergebnisse später veröffentlichen. – Vincent

+0

Eine schnelle Benchmark von 1 Milliarde Aufrufen einer Grundfunktion, die 3 Doppel nimmt und ein Doppel zurückgibt (g ++ -O3), sagt mir, dass es keinen praktischen Unterschied zwischen einem direkten Anruf und meinem "dummen" Beispiel am Anfang gibt. Bei einem Klassenmitglied, das std :: function oder den Template-Klassentrick verwendet, ergibt sich ein Zeitaufwand von ca. 15%. – Vincent

3

Sie müssten einen std::function Wrapper verwenden, möglicherweise lassen den Konstruktor initialisieren es von einem Eingangs aufrufbare Objekt:

#include <functional> // <== REQUIRED FOR std::function 

class MyClass 
{ 
    public: 

     template<typename F> 
     MyClass(F&& f) : _function(std::forward<F>(f)) { } 

     inline double f(double x) 
     { 
      return _function(x); 
     } 

    private: 

     std::function<double(double)> _function;  
}; 

Hinweis, dass dies Ihnen einige Laufzeit-Overhead bringen kann, aber die Design ist einfach und flexibel. Sofern Sie nicht nachweisen können, dass dieser Laufzeit-Overhead ein Engpass für die Leistungsanforderungen Ihrer Software ist, sollte dieser Laufzeit-Overhead keine Rolle spielen.

+0

Ich werde das benchmarken müssen, weil dies für intensive Berechnungen mit wahrscheinlich hunderten von Millionen von Anrufen ist. – Vincent

+0

@Vincent: In der Tat, in diesem Fall sollten Sie es Benchmarks. Wenn das, was Sie übergeben, ein Funktionszeiger oder ein nicht erfassendes Lambda ist (das in einen Funktionszeiger umgewandelt werden kann) und Ihre Implementierung clever genug ist, ist es wahrscheinlich, dass der Aufwand minimal ist. Aber gut, wenn es um Leistung geht, sollte man niemals Annahmen vertrauen, die nicht durch konkrete Messungen unterstützt werden. –

+0

@Vincent Ich habe eine alternative Lösung zu meiner Antwort hinzugefügt, die Sie vielleicht überprüfen möchten. – leemes