Ich höre immer wieder viel über Funktoren in C++. Kann mir jemand einen Überblick darüber geben, was sie sind und in welchen Fällen sie nützlich sein könnten?
Antworten
Zu viele Anzeigen?Kleine Ergänzung. Sie können verwenden boost::function
, um Funktoren aus Funktionen und Methoden zu erstellen, etwa so:
class Foo
{
public:
void operator () (int i) { printf("Foo %d", i); }
};
void Bar(int i) { printf("Bar %d", i); }
Foo foo;
boost::function<void (int)> f(foo);//wrap functor
f(1);//prints "Foo 1"
boost::function<void (int)> b(&Bar);//wrap normal function
b(1);//prints "Bar 1"
und Sie können boost::bind verwenden, um diesem Funktor einen Zustand hinzuzufügen
boost::function<void ()> f1 = boost::bind(foo, 2);
f1();//no more argument, function argument stored in f1
//and this print "Foo 2" (:
//and normal function
boost::function<void ()> b1 = boost::bind(&Bar, 2);
b1();// print "Bar 2"
und am nützlichsten, mit boost::bind und boost::function kann man einen Funktor aus einer Klassenmethode erstellen, eigentlich ist dies ein Delegat:
class SomeClass
{
std::string state_;
public:
SomeClass(const char* s) : state_(s) {}
void method( std::string param )
{
std::cout << state_ << param << std::endl;
}
};
SomeClass *inst = new SomeClass("Hi, i am ");
boost::function< void (std::string) > callback;
callback = boost::bind(&SomeClass::method, inst, _1);//create delegate
//_1 is a placeholder it holds plase for parameter
callback("useless");//prints "Hi, i am useless"
Sie können eine Liste oder einen Vektor von Funktoren erstellen
std::list< boost::function<void (EventArg e)> > events;
//add some events
....
//call them
std::for_each(
events.begin(), events.end(),
boost::bind( boost::apply<void>(), _1, e));
Es gibt nur ein Problem mit all diesem Zeug, Compiler-Fehlermeldungen sind nicht für Menschen lesbar :)
Ein Functor ist ein Objekt, das sich wie eine Funktion verhält. Im Grunde genommen ist es eine Klasse, die definiert operator()
.
class MyFunctor
{
public:
int operator()(int x) { return x * 2;}
}
MyFunctor doubler;
int x = doubler(5);
Der eigentliche Vorteil besteht darin, dass ein Funktor einen Zustand halten kann.
class Matcher
{
int target;
public:
Matcher(int m) : target(m) {}
bool operator()(int x) { return x == target;}
}
Matcher Is5(5);
if (Is5(n)) // same as if (n == 5)
{ ....}
Die Bezeichnung "Funktor" wurde traditionell verwendet in Kategorientheorie lange bevor C++ auf der Bildfläche erschien. Dies hat nichts mit dem C++-Konzept des Funktors zu tun. Es ist besser, den Namen Funktionsobjekt anstelle dessen, was wir in C++ "Funktor" nennen. In anderen Programmiersprachen werden ähnliche Konstrukte so genannt.
Wird anstelle der einfachen Funktion verwendet:
Merkmale:
- Funktionsobjekt kann Zustand haben
- Funktionsobjekt passt in OOP (es verhält sich wie jedes andere Objekt).
Contras:
- Bringt mehr Komplexität in das Programm.
Wird anstelle von Funktionszeigern verwendet:
Merkmale:
- Funktionsobjekte können oft inlined sein
Contras:
- Funktionsobjekte können während der Laufzeit nicht mit anderen Funktionsobjekten ausgetauscht werden (es sei denn, sie erweitern eine Basisklasse, was einen gewissen Overhead bedeutet).
Wird anstelle der virtuellen Funktion verwendet:
Merkmale:
- Funktionsobjekt (nicht-virtuell) erfordert keine vtable und kein Laufzeit-Dispatching, daher ist es in den meisten Fällen effizienter
Contras:
- Funktionsobjekte können während der Laufzeit nicht mit anderen Funktionsobjekten ausgetauscht werden (es sei denn, sie erweitern eine Basisklasse, was einen gewissen Overhead bedeutet).
Für die Neulinge wie mich unter uns: Nach ein wenig Recherche habe ich herausgefunden, was der von Jalf gepostete Code bewirkt.
Ein Funktor ist ein Klassen- oder Strukturobjekt, das wie eine Funktion "aufgerufen" werden kann. Dies wird durch Überladen der Funktion () operator
. Die () operator
(ich bin mir nicht sicher, wie es heißt) kann eine beliebige Anzahl von Argumenten annehmen. Andere Operatoren benötigen nur zwei, z.B. 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 eine () operator
Das verleiht ihm seine Flexibilität.
Um einen Funktor zu erstellen, erstellen Sie zunächst Ihre Klasse. Dann erstellen Sie einen Konstruktor für die Klasse mit einem Parameter Ihrer Wahl von Typ und Name. Darauf folgt in derselben Anweisung eine Initialisierungsliste (die einen einzelnen Doppelpunkt-Operator verwendet, was mir ebenfalls neu war), die die Mitgliedsobjekte der Klasse mit dem zuvor deklarierten Parameter des Konstruktors konstruiert. Dann wird die () operator
überlastet ist. 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 etwas davon ungenau oder einfach nur falsch ist, können Sie mich gerne korrigieren!
Wie bereits von anderen erwähnt, ist ein Funktor ein Objekt, das sich wie eine Funktion verhält, d.h. 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 festhalten können, wie ein Abschluss in funktionalen Sprachen. Sie könnten zum Beispiel einen MultiplyBy
Funktor, der sein Argument mit einem bestimmten 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 eine MultiplyBy
Objekt mit einem Algorithmus wie std::transform:
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 Aufruf in mehr Fällen inlined werden kann. Wenn Sie einen Funktionszeiger an transform
es sei denn que Aufruf eingefügt wurde und der Compiler weiß, dass Sie ihm immer dieselbe Funktion übergeben, kann er den Aufruf nicht über den Zeiger einfügen.
- See previous answers
- Weitere Antworten anzeigen