2013-05-21 10 views
6

Rozważmy następujący przykład: niemySeter funkcji lambda?

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

Dzięki tej klasy, mogę zadzwonić MyClass::f(x, function), z funkcją lambda wykonać go na x, z (mam nadzieję) nie głową. Moje pytanie brzmi: jaki byłby odpowiednik z function jako ustawialnym członkiem 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

Może to być wszystko, co zajmuje jedno podwójne i zwraca podwójne. Możesz użyć 'std :: function '. – juanchopanza

Odpowiedz

10

Funkcje Lambda (jak również niektóre inne typy funkcji "wywoływalnych") można zawijać i przechowywać przy użyciu klasy szablonu std::function, znajdującej się w nagłówku <functional>. Jego parametr szablonu jest podpis funkcja ze składnią

ReturnType(ArgumentType1, ArgumentType2, ...) 

tak w przypadku, gdy cały rodzaj funkcja otoki staje

std::function<double(double)> 

a więc kod staje

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 jest " więcej "niż opakowanie dla wskaźników funkcji. Jak zapewne wiesz, funkcje lambda mogą przechwytywać część kontekstu zmiennych, który musi być gdzieś przechowywany. std::function robi to za Ciebie w sposób przezroczysty.

Pamiętaj, że std::function nie obsługuje przeciążonych podpisów/operatorów połączeń szablonowych dla funktorów. Podczas przypisywania funktora z podpisem operatora, takim jak T operator()(T value), do std::function<double(double)>, można go wywołać tylko z tą sygnaturą. Tak więc nie ma już żadnej wartości, chyba że std::function<T(T)> jest już znany, na przykład parametr szablonu twojej klasy.


alternatywy, która może być bardziej skuteczny w niektórych przypadkach (trzeba odniesienia/profil go), jest, aby cała klasa klasa szablon z parametrem typu funkcją jest parametr szablonu.Następnie można zapisać funkcję członka:

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

W celu stworzenia takiego obiektu, należy określić parametr szablonu, tak:

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

Aby uniknąć tego brzydkiego składniowej napowietrznych, dodać funkcję „maker”, który wykorzystuje typu szablon odliczenia:

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

Następnie kod staje się bardziej czytelny, z wykorzystaniem auto ponownie:

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

Podoba mi się Twoja metoda klasy szablonów. Porównywam to wszystko i publikuję wyniki później. – Vincent

+0

Szybki test 1 miliarda wywołań funkcji podstawowej, która zajmuje 3 podwójne i zwrócenie podwójnej (g ++ -O3), mówi mi, że nie ma praktycznej różnicy między bezpośrednim połączeniem a moim "niemym" przykładem na początku. W przypadku członka klasy używającego funkcji std :: lub sztuczki na szablonach około 15% czasu jest narzutem. – Vincent

3

Trzeba by użyć std::function opakowanie, ewentualnie pozwalając konstruktor zainicjować go od wejścia na żądanie obiektu:

#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;  
}; 

zawiadomieniu, że może to przynieść Ci trochę czasu wykonywania narzutu, lecz projekt jest prosty i elastyczny. O ile nie można udowodnić, że ten czas pracy w czasie rzeczywistym jest wąskim gardłem w zakresie wymagań wydajnościowych oprogramowania, ten czas pracy nie powinien stanowić problemu.

+0

Będę musiał to sprawdzić, ponieważ jest przeznaczony do intensywnych obliczeń z prawdopodobnie setkami milionów połączeń. – Vincent

+0

@Vincent: Rzeczywiście, w takim przypadku powinieneś to porównać. Jeśli to, co przechodzisz, jest wskaźnikiem funkcji lub nie przechwytującą wartością lambda (która może zostać przekonwertowana na wskaźnik funkcji), a implementacja jest wystarczająco inteligentna, prawdopodobnie obciążenie będzie minimalne. Ale cóż, jeśli chodzi o wydajność, nigdy nie należy ufać założeniom, które nie są poparte konkretnymi pomiarami. –

+0

@ Vincent Dodałem alternatywne rozwiązanie do mojej odpowiedzi, możesz to sprawdzić. – leemes

Powiązane problemy