template - functor c++




C++ Funktoren-und ihre Verwendung (10)

Ich höre immer wieder viel über Funktoren in C ++. Kann mir jemand einen Überblick geben, was sie sind und in welchen Fällen sie nützlich wären?


Abgesehen davon, dass sie in Callbacks verwendet werden, können C ++ - Funktoren auch dazu beitragen, einen Matlab- ähnlichen Zugriffsstil für eine Matrixklasse bereitzustellen. Es gibt ein example .


Der Name "funktor" wurde schon lange vor C ++ in der Kategorientheorie verwendet . Das hat nichts mit C ++ - Konzept von funktor zu tun. Es ist besser, das name- function-Objekt anstelle von dem zu verwenden, was wir in C ++ "functor" nennen. So rufen andere Programmiersprachen ähnliche Konstrukte auf.

Verwendet anstelle der einfachen Funktion:

Eigenschaften:

  • Das Funktionsobjekt kann einen Status haben
  • Funktionsobjekt passt in OOP (es verhält sich wie jedes andere Objekt).

Nachteile:

  • Bringt mehr Komplexität in das Programm.

Wird anstelle des Funktionszeigers verwendet:

Eigenschaften:

  • Das Funktionsobjekt kann oft inline sein

Nachteile:

  • Das Funktionsobjekt kann während der Laufzeit nicht durch einen anderen Funktionsobjekttyp ersetzt werden (zumindest nicht, wenn eine Basisklasse erweitert wird, was zu einem Mehraufwand führt).

Verwendet anstelle der virtuellen Funktion:

Eigenschaften:

  • Das Funktionsobjekt (nicht virtuell) benötigt keine Vtable- und Runtime-Verteilung, daher ist es in den meisten Fällen effizienter

Nachteile:

  • Das Funktionsobjekt kann während der Laufzeit nicht durch einen anderen Funktionsobjekttyp ersetzt werden (zumindest nicht, wenn eine Basisklasse erweitert wird, was zu einem Mehraufwand führt).

Ein Funktor ist eigentlich nur eine Klasse, die den Operator () definiert. Damit können Sie Objekte erstellen, die wie eine Funktion aussehen:

// this is a functor
struct add_x {
  add_x(int x) : x(x) {}
  int operator()(int y) const { return x + y; }

private:
  int x;
};

// Now you can use it like this:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument

std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out(in.size());
// Pass a functor to std::transform, which calls the functor on every element 
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1)); 
assert(out[i] == in[i] + 1); // for all i

Es gibt ein paar nette Dinge über Funktoren. Eine davon ist, dass sie im Gegensatz zu regulären Funktionen einen Zustand enthalten können. Das obige Beispiel erstellt eine Funktion, die 42 zu dem hinzufügt, was Sie ihm geben. Aber dieser Wert 42 ist nicht fest codiert, er wurde beim Erstellen unserer Funktorinstanz als Konstruktorargument angegeben. Ich könnte einen anderen Addierer erstellen, der 27 hinzufügt, indem ich einfach den Konstruktor mit einem anderen Wert anrufe. Dies macht sie schön anpassbar.

Wie die letzten Zeilen zeigen, übergibt man Funktoren oft als Argumente an andere Funktionen wie std :: transform oder die anderen Standard-Bibliotheksalgorithmen. Sie könnten dasselbe mit einem regulären Funktionszeiger machen, außer dass, wie ich oben sagte, Funktoren "angepasst" werden können, weil sie einen Zustand enthalten, der sie flexibler macht (Wenn ich einen Funktionszeiger verwenden wollte, müsste ich eine Funktion schreiben was genau 1 zu seinem Argument hinzugefügt hat. Der Funktor ist allgemein und fügt hinzu, was auch immer du initialisiert hast), und sie sind auch potentiell effizienter. Im obigen Beispiel weiß der Compiler genau, welche Funktion std::transform aufrufen soll. Es sollte add_x::operator() aufrufen. Das bedeutet, dass dieser Funktionsaufruf inline eingebunden werden kann. Und das macht es genauso effizient, als ob ich die Funktion für jeden Wert des Vektors manuell aufgerufen hätte.

Wenn ich stattdessen einen Funktionszeiger übergeben hätte, könnte der Compiler nicht sofort sehen, auf welche Funktion er zeigt. Wenn er also einige ziemlich komplexe globale Optimierungen ausführt, müsste er den Zeiger zur Laufzeit dereferenzieren und dann den Aufruf ausführen.


Ein Funktor ist eine Funktion höherer Ordnung , die eine Funktion auf parametrisierte (dh gestützte) Typen anwendet. Es ist eine Verallgemeinerung der map höherer Ordnung. Zum Beispiel könnten wir einen Funktor für std::vector wie std::vector :

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
    std::vector<U> result;
    std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
    return result;
}

Diese Funktion verwendet einen std::vector<T> und gibt std::vector<U> wenn eine Funktion F , die ein T akzeptiert und ein U zurückgibt. Ein Funktor muss nicht über Containertypen definiert werden, er kann auch für alle Vorlagen definiert werden, einschließlich std::shared_ptr :

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
    if (p == nullptr) return nullptr;
    else return std::shared_ptr<U>(new U(f(*p)));
}

Hier ist ein einfaches Beispiel, das den Typ in ein double konvertiert:

double to_double(int x)
{
    return x;
}

std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);

std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);

Es gibt zwei Gesetze, denen Funktoren folgen sollten. Das erste ist das Identitätsgesetz, das besagt, dass, wenn der Funktor eine Identitätsfunktion erhält, dieselbe wie die Identitätsfunktion auf den Typ fmap(identity, x) werden soll, das heißt, fmap(identity, x) sollte dasselbe wie identity(x) :

struct identity_f
{
    template<class T>
    T operator()(T x) const
    {
        return x;
    }
};
identity_f identity = {};

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);

Das nächste Gesetz ist das Zusammensetzungsgesetz, das besagt, dass, wenn der Funktor eine Zusammensetzung von zwei Funktionen erhält, dieselbe sein sollte wie die Anwendung des Funktors für die erste Funktion und dann wieder für die zweite Funktion. Also sollte fmap(std::bind(f, std::bind(g, _1)), x) dasselbe wie fmap(f, fmap(g, x)) :

double to_double(int x)
{
    return x;
}

struct foo
{
    double x;
};

foo to_foo(double x)
{
    foo r;
    r.x = x;
    return r;
}

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));

Für die Neulinge wie mich unter uns: Nach ein wenig Recherche habe ich herausgefunden, was der Code jalf getan hat.

Ein Funktor ist ein Klassen- oder Strukturobjekt, das wie eine Funktion "aufgerufen" werden kann. Dies wird durch Überladen des () operator . Der () operator (nicht sicher, wie er aufgerufen wurde) kann eine beliebige Anzahl von Argumenten annehmen. Andere Operatoren nehmen nur zwei, dh der + operator kann nur zwei Werte annehmen (einen auf jeder Seite des Operators) und den Wert zurückgeben, für den Sie ihn überladen haben. Sie können eine beliebige Anzahl von Argumenten in einen () operator einfügen, was ihm seine Flexibilität gibt.

Um einen Funktor zu erstellen, erstellen Sie zuerst Ihre Klasse. Dann erstellen Sie einen Konstruktor für die Klasse mit einem Parameter Ihrer Wahl von Typ und Name. Diesem folgt in der gleichen Anweisung eine Initialisierungsliste (die einen einzelnen Doppelpunktoperator verwendet, etwas, zu dem ich auch neu bin), das die Klassenmemberobjekte mit dem zuvor deklarierten Parameter für den Konstruktor konstruiert. Dann ist der () operator überladen. Schließlich deklarieren Sie die privaten Objekte der Klasse oder Struktur, die Sie erstellt haben.

Mein Code (Ich fand die Variablennamen von jalf verwirrend)

class myFunctor
{ 
    public:
        /* myFunctor is the constructor. parameterVar is the parameter passed to
           the constructor. : is the initializer list operator. myObject is the
           private member object of the myFunctor class. parameterVar is passed
           to the () operator which takes it and adds it to myObject in the
           overloaded () operator function. */
        myFunctor (int parameterVar) : myObject( parameterVar ) {}

        /* the "operator" word is a keyword which indicates this function is an 
           overloaded operator function. The () following this just tells the
           compiler that () is the operator being overloaded. Following that is
           the parameter for the overloaded operator. This parameter is actually
           the argument "parameterVar" passed by the constructor we just wrote.
           The last part of this statement is the overloaded operators body
           which adds the parameter passed to the member object. */
        int operator() (int myArgument) { return myObject + myArgument; }

    private: 
        int myObject; //Our private member object.
}; 

Wenn irgendetwas davon falsch oder einfach falsch ist, fühlen Sie sich frei, mich zu korrigieren!


Functor kann auch verwendet werden, um die Definition einer lokalen Funktion innerhalb einer Funktion zu simulieren. Siehe die question und ein another .

Ein lokaler Funktor kann jedoch nicht auf externe Auto-Variablen zugreifen. Die Lambda (C ++ 11) -Funktion ist eine bessere Lösung.


Hier ist eine Situation, in der ich gezwungen war, einen Functor zu benutzen, um mein Problem zu lösen:

Ich habe eine Reihe von Funktionen (sagen wir, 20 davon), und sie sind alle identisch, außer dass jede eine spezifische Funktion an 3 spezifischen Stellen aufruft.

Das ist unglaubliche Verschwendung und Code-Duplizierung. Normalerweise würde ich einfach einen Funktionszeiger übergeben und das an den 3 Stellen aufrufen. (Der Code muss also nur einmal anstelle von zwanzig Mal angezeigt werden.)

Aber dann habe ich festgestellt, dass für die jeweilige Funktion jeweils ein völlig anderes Parameterprofil benötigt wird! Manchmal 2 Parameter, manchmal 5 Parameter usw.

Eine andere Lösung wäre eine Basisklasse, bei der die spezifische Funktion eine überschriebene Methode in einer abgeleiteten Klasse ist. Aber möchte ich wirklich all diese ERBSCHAFT aufbauen, nur damit ich einen Funktionszeiger übergeben kann ????

LÖSUNG: Also, was ich gemacht habe, war, ich habe eine Wrapper-Klasse (ein "Functor") erstellt, die in der Lage ist, jede der Funktionen aufzurufen, die ich aufgerufen habe. Ich habe es im Voraus eingerichtet (mit seinen Parametern usw.) und gebe es dann anstelle eines Funktionszeigers ein. Nun kann der aufgerufene Code den Functor auslösen, ohne zu wissen, was im Inneren passiert. Es kann sogar mehrere Male angerufen werden (ich musste es dreimal anrufen)

Das ist es - ein praktisches Beispiel, bei dem sich ein Functor als die offensichtliche und einfache Lösung erwies, mit der ich die Codeverdopplung von 20 Funktionen auf 1 reduzieren konnte.


Ich habe einen sehr interessanten Gebrauch von Funktoren "entdeckt": Ich benutze sie, wenn ich keinen guten Namen für eine Methode habe, da ein Funktor eine Methode ohne Namen ist ;-)


Um hinzuzufügen, habe ich Funktionsobjekte verwendet, um eine vorhandene Legacy-Methode an das Befehlsmuster anzupassen. (nur Platz, wo die Schönheit des OO Paradigmas wahr OCP Ich fühlte); Fügen Sie hier auch das zugehörige Funktionsadaptermuster hinzu.

Angenommen, Ihre Methode hat die Signatur:

int CTask::ThreeParameterTask(int par1, int par2, int par3)

Wir werden sehen, wie wir es für das Befehlsmuster anpassen können. Dazu müssen Sie zuerst einen Mitgliedsfunktionsadapter schreiben, damit er als Funktionsobjekt aufgerufen werden kann.

Beachte - das ist hässlich, und vielleicht kannst du die Boost-Bind-Helfer benutzen, aber wenn du das nicht kannst oder willst, ist das eine Möglichkeit.

// a template class for converting a member function of the type int        function(int,int,int)
//to be called as a function object
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
class mem_fun3_t
{
  public:
explicit mem_fun3_t(_Ret (_Class::*_Pm)(_arg1,_arg2,_arg3))
    :m_Ptr(_Pm) //okay here we store the member function pointer for later use
    {}

//this operator call comes from the bind method
_Ret operator()(_Class *_P, _arg1 arg1, _arg2 arg2, _arg3 arg3) const
{
    return ((_P->*m_Ptr)(arg1,arg2,arg3));
}
private:
_Ret (_Class::*m_Ptr)(_arg1,_arg2,_arg3);// method pointer signature
};

Außerdem benötigen wir eine Hilfsmethode mem_fun3 für die oben genannte Klasse, um den Aufruf zu erleichtern.

template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3> mem_fun3 ( _Ret (_Class::*_Pm)          (_arg1,_arg2,_arg3) )
{
  return (mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3>(_Pm));

}

Um nun die Parameter zu binden, müssen wir eine Binder-Funktion schreiben. Also, hier geht es:

template<typename _Func,typename _Ptr,typename _arg1,typename _arg2,typename _arg3>
class binder3
{
public:
//This is the constructor that does the binding part
binder3(_Func fn,_Ptr ptr,_arg1 i,_arg2 j,_arg3 k)
    :m_ptr(ptr),m_fn(fn),m1(i),m2(j),m3(k){}

 //and this is the function object 
 void operator()() const
 {
        m_fn(m_ptr,m1,m2,m3);//that calls the operator
    }
private:
    _Ptr m_ptr;
    _Func m_fn;
    _arg1 m1; _arg2 m2; _arg3 m3;
};

Und, eine Hilfsfunktion, um die Binder3-Klasse zu verwenden - bind3:

//a helper function to call binder3
template <typename _Func, typename _P1,typename _arg1,typename _arg2,typename _arg3>
binder3<_Func, _P1, _arg1, _arg2, _arg3> bind3(_Func func, _P1 p1,_arg1 i,_arg2 j,_arg3 k)
{
    return binder3<_Func, _P1, _arg1, _arg2, _arg3> (func, p1,i,j,k);
}

Jetzt müssen wir dies mit der Command-Klasse verwenden; verwende den folgenden typedef:

typedef binder3<mem_fun3_t<int,T,int,int,int> ,T* ,int,int,int> F3;
//and change the signature of the ctor
//just to illustrate the usage with a method signature taking more than one parameter
explicit Command(T* pObj,F3* p_method,long timeout,const char* key,
long priority = PRIO_NORMAL ):
m_objptr(pObj),m_timeout(timeout),m_key(key),m_value(priority),method1(0),method0(0),
method(0)
{
    method3 = p_method;
}

So nennst du es:

F3 f3 = PluginThreadPool::bind3( PluginThreadPool::mem_fun3( 
      &CTask::ThreeParameterTask), task1,2122,23 );

Hinweis: f3 (); wird die Methode task1-> ThreeParameterTask (21,22,23); aufrufen.

Der vollständige Kontext dieses Musters finden Sie unter folgendem link


Wie andere bereits erwähnt haben, ist ein Funktor ein Objekt, das sich wie eine Funktion verhält, dh es überlädt den Funktionsaufrufoperator.

Funktoren werden häufig in STL-Algorithmen verwendet. Sie sind nützlich, weil sie den Zustand vor und zwischen Funktionsaufrufen halten können, wie eine Schließung in funktionalen Sprachen. Beispielsweise könnten Sie einen MultiplyBy Funktor definieren, der sein Argument mit einem angegebenen Betrag multipliziert:

class MultiplyBy {
private:
    int factor;

public:
    MultiplyBy(int x) : factor(x) {
    }

    int operator () (int other) const {
        return factor * other;
    }
};

Dann könnten Sie ein MultiplyBy Objekt an einen Algorithmus wie std :: transform übergeben:

int array[5] = {1, 2, 3, 4, 5};
std::transform(array, array + 5, array, MultiplyBy(3));
// Now, array is {3, 6, 9, 12, 15}

Ein weiterer Vorteil eines Funktors gegenüber einem Zeiger auf eine Funktion besteht darin, dass der Anruf in mehreren Fällen inline ausgeführt werden kann. Wenn Sie einen Funktionszeiger an die transform , es sei denn, dieser Aufruf wurde inline und der Compiler weiß, dass Sie immer die gleiche Funktion übergeben, kann er den Aufruf nicht über den Zeiger inline.





function-call-operator