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?
876
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?
operator()(...)
bedeutet: Der Operator "Funktionsaufruf" wird überlastet . Es ist einfach eine Überlastung des()
Bedieners für den Bediener. Verwechseln Sie nicht denoperator()
Aufruf einer aufgerufenen Funktionoperator
, sondern betrachten Sie sie als die übliche Syntax zum Überladen von Operatoren.Antworten:
Ein Funktor ist so ziemlich nur eine Klasse, die den Operator () definiert. Auf diese Weise können Sie Objekte erstellen, die wie eine Funktion "aussehen":
Es gibt ein paar nette Dinge über Funktoren. Zum einen können sie im Gegensatz zu regulären Funktionen den Status enthalten. Das obige Beispiel erstellt eine Funktion, die 42 zu dem hinzufügt, was Sie ihm geben. Dieser Wert 42 ist jedoch nicht fest codiert. Er wurde als Konstruktorargument angegeben, als wir unsere Funktorinstanz erstellt haben. Ich könnte einen weiteren Addierer erstellen, der 27 hinzufügt, indem ich einfach den Konstruktor mit einem anderen Wert aufrufe. Dies macht sie schön anpassbar.
Wie die letzten Zeilen zeigen, übergeben Sie häufig Funktoren als Argumente an andere Funktionen wie std :: transform oder die anderen Standardbibliotheksalgorithmen. Sie können dasselbe mit einem regulären Funktionszeiger tun, außer dass Funktoren, wie oben erwähnt, "angepasst" werden können, weil sie den Status enthalten, wodurch sie flexibler werden (Wenn ich einen Funktionszeiger verwenden möchte, muss ich eine Funktion schreiben Der Funktor ist allgemein und fügt alles hinzu, womit Sie ihn initialisiert haben. Außerdem sind sie möglicherweise effizienter. Im obigen Beispiel weiß der Compiler genau, welche Funktion
std::transform
aufgerufen werden soll. Es sollte anrufenadd_x::operator()
. Das heißt, es kann diesen Funktionsaufruf inline. Und das macht es genauso effizient, als hätte ich die Funktion für jeden Wert des Vektors manuell aufgerufen.Wenn ich stattdessen einen Funktionszeiger übergeben hätte, könnte der Compiler nicht sofort erkennen, auf welche Funktion er verweist. Wenn er also keine recht komplexen globalen Optimierungen durchführt, müsste er den Zeiger zur Laufzeit dereferenzieren und dann den Aufruf ausführen.
quelle
add42
, hätte ich den zuvor erstellten Funktor verwendet und jedem Wert 42 hinzugefügt. Mitadd_x(1)
erstelle ich eine neue Instanz des Funktors, die jedem Wert nur 1 hinzufügt. Es ist einfach zu zeigen, dass Sie den Funktor häufig "on the fly" instanziieren, wenn Sie ihn benötigen, anstatt ihn zuerst zu erstellen und zu behalten, bevor Sie ihn tatsächlich für irgendetwas verwenden.operator()
, denn das ist es, was der Anrufer verwendet, um es aufzurufen. Was der Funktor sonst noch von Elementfunktionen, Konstruktoren, Operatoren und Elementvariablen hat, liegt ganz bei Ihnen.add42
würde man einen Funktor nennen, nichtadd_x
(das ist die Klasse des Funktors oder nur die Funktorklasse). Ich finde diese Terminologie konsistent, weil Funktoren auch Funktionsobjekte genannt werden , keine Funktionsklassen. Können Sie diesen Punkt klarstellen?Kleine Ergänzung. Mit folgenden Funktionen können Sie
boost::function
Funktoren aus folgenden Funktionen und Methoden erstellen:und Sie können boost :: bind verwenden, um diesem Funktor einen Status hinzuzufügen
und am nützlichsten ist, dass Sie mit boost :: bind und boost :: function einen Funktor aus der Klassenmethode erstellen können. Eigentlich ist dies ein Delegat:
Sie können eine Liste oder einen Vektor von Funktoren erstellen
Es gibt ein Problem mit all dem Zeug, Compiler-Fehlermeldungen sind nicht von Menschen lesbar :)
quelle
operator ()
in Ihrem ersten Beispiel nicht öffentlich sein, da Klassen standardmäßig privat sind?Ein Functor ist ein Objekt, das wie eine Funktion wirkt. Grundsätzlich eine Klasse, die definiert
operator()
.Der wahre Vorteil ist, dass ein Funktor den Zustand halten kann.
quelle
int
wenn er zurückkehren solltebool
? Dies ist C ++, nicht C. Als diese Antwort geschrieben wurde, gab es siebool
nicht?Der Name "Funktor" wurde traditionell in der Kategorietheorie verwendet, lange bevor C ++ auf der Bühne erschien. Dies hat nichts mit dem C ++ - Konzept des Funktors zu tun. Es ist besser , zu verwenden Name Funktionsobjekt anstelle dessen , was wir „Funktor“ in C ++ aufrufen. So nennen andere Programmiersprachen ähnliche Konstrukte.
Wird anstelle der einfachen Funktion verwendet:
Eigenschaften:
Nachteile:
Wird anstelle des Funktionszeigers verwendet:
Eigenschaften:
Nachteile:
Wird anstelle der virtuellen Funktion verwendet:
Eigenschaften:
Nachteile:
quelle
foo(arguments)
. Daher kann es Variablen enthalten; Wenn Sie beispielsweise eineupdate_password(string)
Funktion hatten, möchten Sie möglicherweise nachverfolgen, wie oft dies geschehen ist. Mit einem Funktor kann diesprivate long time
den Zeitstempel darstellen, der zuletzt passiert ist. Mit einem Funktionszeiger oder einer einfachen Funktion müssten Sie eine Variable außerhalb ihres Namespace verwenden, die nur durch Dokumentation und Verwendung direkt in Beziehung steht und nicht durch Definition.lWie andere bereits erwähnt haben, ist ein Funktor ein Objekt, das sich wie eine Funktion verhält, dh den Funktionsaufrufoperator überlastet.
Funktoren werden üblicherweise in STL-Algorithmen verwendet. Sie sind nützlich, weil sie den Status vor und zwischen Funktionsaufrufen halten können, wie z. B. ein Abschluss in funktionalen Sprachen. Sie können beispielsweise einen
MultiplyBy
Funktor definieren , der sein Argument mit einem bestimmten Betrag multipliziert:Dann könnten Sie ein
MultiplyBy
Objekt an einen Algorithmus wie std :: transform übergeben:Ein weiterer Vorteil eines Funktors gegenüber einem Zeiger auf eine Funktion besteht darin, dass der Aufruf in mehreren Fällen eingebunden werden kann. Wenn Sie einen Funktionszeiger an übergeben haben, kann der Aufruf nicht über den Zeiger inline sein, es
transform
sei denn, dieser Aufruf wurde eingebunden und der Compiler weiß, dass Sie ihm immer dieselbe Funktion übergeben.quelle
Für die Neulinge wie mich unter uns: Nach ein wenig Recherche habe ich herausgefunden, was der Code, den Jalf gepostet hat, bewirkt hat.
Ein Funktor ist ein Klassen- oder Strukturobjekt, das wie eine Funktion "aufgerufen" werden kann. Möglich wird dies durch Überlastung der
() operator
. Der() operator
(nicht sicher, wie er heißt) kann eine beliebige Anzahl von Argumenten annehmen. Andere Operatoren nehmen nur zwei, dh sie+ operator
können 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 ein Argument einfügen,() operator
was ihm seine Flexibilität verleiht.Um einen Funktor zu erstellen, erstellen Sie zuerst Ihre Klasse. Anschließend erstellen Sie einen Konstruktor für die Klasse mit einem Parameter Ihrer Wahl von Typ und Name. In derselben Anweisung folgt eine Initialisierungsliste (die einen einzelnen Doppelpunktoperator verwendet, was mir ebenfalls neu war), die die Klassenmitgliedsobjekte mit dem zuvor für den Konstruktor deklarierten Parameter erstellt. Dann
() operator
ist das ü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)
Wenn irgendetwas davon ungenau oder einfach falsch ist, können Sie mich gerne korrigieren!
quelle
Ein Funktor ist eine Funktion höherer Ordnung , die eine Funktion auf die parametrisierten (dh Vorlagen-) Typen anwendet. Es ist eine Verallgemeinerung der Kartenfunktion höherer Ordnung. Zum Beispiel könnten wir einen Funktor für Folgendes definieren
std::vector
:Diese Funktion nimmt a
std::vector<T>
und gibt zurück,std::vector<U>
wenn eine Funktion gegeben istF
, die a nimmtT
und a zurückgibtU
. Ein Funktor muss nicht über Containertypen definiert werden, sondern kann auch für jeden Schablonentyp definiert werden, einschließlichstd::shared_ptr
:Hier ist ein einfaches Beispiel, das den Typ in a konvertiert
double
:Es gibt zwei Gesetze, denen Funktoren folgen sollten. Das erste ist das Identitätsgesetz, das besagt, dass, wenn dem Funktor eine Identitätsfunktion zugewiesen wird, dies dasselbe sein sollte wie das Anwenden der Identitätsfunktion auf den Typ,
fmap(identity, x)
dh dasselbe wieidentity(x)
:Das nächste Gesetz ist das Kompositionsgesetz, das besagt, dass, wenn dem Funktor eine Zusammensetzung aus zwei Funktionen gegeben wird, dies dasselbe sein sollte wie das Anwenden des Funktors für die erste Funktion und dann erneut für die zweite Funktion. Also
fmap(std::bind(f, std::bind(g, _1)), x)
sollte das gleiche sein wiefmap(f, fmap(g, x))
:quelle
fmap(id, x) = id(x)
undfmap(f ◦ g, x) = fmap(f, fmap(g, x))
.Hier ist eine tatsächliche Situation, in der ich gezwungen war, einen Functor zu verwenden, um mein Problem zu lösen:
Ich habe eine Reihe von Funktionen (sagen wir 20), und sie sind alle identisch, außer dass jede an 3 bestimmten Stellen eine andere spezifische Funktion aufruft.
Dies ist eine unglaubliche Verschwendung und Codeduplizierung. Normalerweise würde ich nur einen Funktionszeiger übergeben und das einfach an den 3 Stellen aufrufen. (Der Code muss also nur einmal statt zwanzigmal erscheinen.)
Aber dann wurde mir klar, dass für die jeweilige Funktion jeweils ein völlig anderes Parameterprofil erforderlich war! 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 Vererbung aufbauen, nur damit ich einen Funktionszeiger übergeben kann?
LÖSUNG: Also habe ich eine Wrapper-Klasse (einen "Functor") erstellt, die alle aufgerufenen Funktionen aufrufen kann. Ich richte es im Voraus ein (mit seinen Parametern usw.) und übergebe es dann anstelle eines Funktionszeigers. Jetzt kann der aufgerufene Code den Functor auslösen, ohne zu wissen, was im Inneren passiert. Es kann es sogar mehrmals anrufen (ich brauchte es, um dreimal anzurufen.)
Das war's - ein praktisches Beispiel, bei dem sich ein Functor als offensichtliche und einfache Lösung herausstellte, mit der ich die Codeduplizierung von 20 Funktionen auf 1 reduzieren konnte.
quelle
Mit Ausnahme des in Callback verwendet wird , kann C ++ functors auch helfen , eine schaffen , Matlab mag Zugang Stil zu einer Matrix - Klasse. Es gibt ein Beispiel .
quelle
operator()
jedoch keine Verwendung von Funktionsobjekteigenschaften.Wie bereits wiederholt, sind Funktoren Klassen, die als Funktionen behandelt werden können (Überlastoperator ()).
Sie sind am nützlichsten für Situationen, in denen Sie einige Daten mit wiederholten oder verzögerten Aufrufen einer Funktion verknüpfen müssen.
Beispielsweise könnte eine verknüpfte Liste von Funktoren verwendet werden, um ein grundlegendes synchrones Coroutine-System mit geringem Overhead, einen Task-Dispatcher oder eine unterbrechbare Dateianalyse zu implementieren. Beispiele:
Natürlich sind diese Beispiele an sich nicht so nützlich. Sie zeigen nur, wie nützlich Funktoren sein können, die Funktoren selbst sind sehr einfach und unflexibel, und dies macht sie weniger nützlich als zum Beispiel, was Boost bietet.
quelle
Funktoren werden in gtkmm verwendet, um eine GUI-Schaltfläche mit einer tatsächlichen C ++ - Funktion oder -Methode zu verbinden.
Wenn Sie die pthread-Bibliothek verwenden, um Ihre App multithreaded zu machen, kann Functors Ihnen helfen.
Um einen Thread zu starten, ist eines der Argumente von
pthread_create(..)
der Funktionszeiger, der in seinem eigenen Thread ausgeführt werden soll.Aber es gibt eine Unannehmlichkeit. Dieser Zeiger kann kein Zeiger auf eine Methode sein, es sei denn, es handelt sich um eine statische Methode oder Sie geben eine Klasse wie an
class::method
. Und eine andere Sache, die Schnittstelle Ihrer Methode kann nur sein:Sie können also keine Methoden aus Ihrer Klasse (auf einfache und offensichtliche Weise) in einem Thread ausführen, ohne etwas Besonderes zu tun.
Eine sehr gute Möglichkeit, mit Threads in C ++ umzugehen, besteht darin, eine eigene
Thread
Klasse zu erstellen . Wenn Sie Methoden aus derMyClass
Klasse ausführen wollten , wandelte ich diese Methoden inFunctor
abgeleitete Klassen um.Die
Thread
Klasse verfügt außerdem über die folgende Methode:static void* startThread(void* arg)
Ein Zeiger auf diese Methode wird als aufzurufendes Argument verwendet
pthread_create(..)
. Und wasstartThread(..)
in arg empfangen werden sollte, ist einvoid*
umwandelnder Verweis auf eine Instanz im Heap einerFunctor
abgeleiteten Klasse, dieFunctor*
bei der Ausführung zurückgesetzt und dann alsrun()
Methode bezeichnet wird.quelle
Zum Hinzufügen habe ich Funktionsobjekte verwendet, um eine vorhandene Legacy-Methode an das Befehlsmuster anzupassen. (einziger Ort, an dem ich die Schönheit des OO-Paradigmas als wahres OCP empfand); Fügen Sie hier auch das zugehörige Funktionsadaptermuster hinzu.
Angenommen, Ihre Methode hat die Signatur:
Wir werden sehen, wie wir es für das Befehlsmuster anpassen können - dazu müssen Sie zuerst einen Elementfunktionsadapter schreiben, damit es als Funktionsobjekt aufgerufen werden kann.
Hinweis - Dies ist hässlich und möglicherweise können Sie die Boost-Bindungshelfer usw. verwenden. Wenn Sie dies jedoch nicht können oder möchten, ist dies eine Möglichkeit.
Außerdem benötigen wir eine Hilfsmethode mem_fun3 für die obige Klasse, um den Aufruf zu erleichtern.
}}
Um die Parameter zu binden, müssen wir nun eine Binder-Funktion schreiben. Also, hier geht es:
Und eine Hilfsfunktion zur Verwendung der Klasse binder3 - bind3:
Jetzt müssen wir dies mit der Command-Klasse verwenden. Verwenden Sie das folgende typedef:
So nennen Sie es:
Anmerkung: f3 (); ruft die Methode task1-> ThreeParameterTask (21,22,23) auf;.
Den vollständigen Kontext dieses Musters finden Sie unter folgendem Link
quelle
Ein großer Vorteil der Implementierung von Funktionen als Funktoren besteht darin, dass sie den Status zwischen Aufrufen beibehalten und wiederverwenden können. Beispielsweise arbeiten viele dynamische Programmieralgorithmen, wie der Wagner-Fischer-Algorithmus zur Berechnung des Levenshtein-Abstands zwischen Zeichenfolgen, durch Ausfüllen einer großen Ergebnistabelle. Es ist sehr ineffizient, diese Tabelle bei jedem Aufruf der Funktion zuzuweisen. Wenn Sie also die Funktion als Funktor implementieren und die Tabelle zu einer Mitgliedsvariablen machen, kann dies die Leistung erheblich verbessern.
Nachfolgend finden Sie ein Beispiel für die Implementierung des Wagner-Fischer-Algorithmus als Funktor. Beachten Sie, wie die Tabelle im Konstruktor zugewiesen und dann wieder verwendet wird
operator()
, wobei die Größe nach Bedarf geändert wird.quelle
Functor kann auch verwendet werden, um das Definieren einer lokalen Funktion innerhalb einer Funktion zu simulieren. Beziehen Sie sich auf die Frage und eine andere .
Ein lokaler Funktor kann jedoch nicht auf externe automatische Variablen zugreifen. Die Lambda-Funktion (C ++ 11) ist eine bessere Lösung.
quelle
Ich habe eine sehr interessante Verwendung von Funktoren "entdeckt": Ich benutze sie, wenn ich keinen guten Namen für eine Methode habe, da ein Funktor eine Methode ohne Namen ist ;-)
quelle