35

In der Bibliothek Boost Signals überladen sie den Operator().Warum den Operator überschreiben()?

Ist das eine Konvention in C++? Für Rückrufe usw.?

Ich habe das im Code eines Kollegen gesehen (der zufällig ein großer Boost-Fan ist). Von all der Boost-Güte da draußen hat das nur zu Verwirrung für mich geführt.

Irgendwelche Erkenntnisse über den Grund für diese Überlastung?

+1

In Verbindung stehende http://stackoverflow.com/questions/356950/c-functors-and-their-uses? – Konrad

Antwort

100

Einer der primären Ziel, wenn Betreiber Überlastung() ist ein Funktor zu erstellen. Ein Funktor verhält sich wie eine Funktion, hat aber den Vorteil, dass er zustandsbehaftet ist, dh er kann die Daten zwischen den Aufrufen in seinem Zustand halten.

Hier ist ein einfaches Funktors Beispiel:

struct Accumulator 
{ 
    int counter = 0; 
    int operator()(int i) { return counter += i; } 
} 
... 
Accumulator acc; 
cout << acc(10) << endl; //prints "10" 
cout << acc(20) << endl; //prints "30" 

Funktoren mit generischen Programmierung stark genutzt werden. Viele STL-Algorithmen sind sehr allgemein geschrieben, so dass Sie Ihre eigene Funktion/Funktor in den Algorithmus einbinden können. Mit dem Algorithmus std :: for_each können Sie beispielsweise eine Operation für jedes Element eines Bereichs anwenden. Es könnte so etwas implementiert werden:

Sie sehen, dass dieser Algorithmus sehr generisch ist, da er durch eine Funktion parametrisiert ist. Wenn Sie den Operator() verwenden, können Sie mit dieser Funktion entweder einen Funktor oder einen Funktionszeiger verwenden. Hier ist ein Beispiel beide Möglichkeiten zeigt:

void print(int i) { std::cout << i << std::endl; } 
...  
std::vector<int> vec; 
// Fill vec 

// Using a functor 
Accumulator acc; 
std::for_each(vec.begin(), vec.end(), acc); 
// acc.counter contains the sum of all elements of the vector 

// Using a function pointer 
std::for_each(vec.begin(), vec.end(), print); // prints all elements 

In Bezug auf Ihre Frage zu operator() Überlastung, gut ja ist es möglich. Sie können einen Funktor mit mehreren Klammeroperatoren perfekt schreiben, solange Sie die Grundregeln des Methodenüberladens beachten (z. B. ist das Überladen nur für den Rückgabetyp nicht möglich).

+0

Ich denke, ein großer Teil dieser Antwort ist die Syntax der STL for_each. Durch Verwendung des operator() als Operationsteil des Funktors wird es mit der STL gut funktionieren. – JeffV

+0

Es scheint, dass, wenn die STL als do() {...} anstelle von operator()() {...} implementiert wäre, stattdessen verwendet würde. – JeffV

+3

Ein weiterer (meist geringfügiger) Vorteil von Funktoren gegenüber Funktionen ist, dass sie trivial inline sein können. Es gibt keine Zeiger-Indirektion, die nur eine (nichtvirtuelle) Elementfunktion für eine Klasse aufruft, so dass der Compiler bestimmen kann, welche Funktion aufgerufen wird, und dies inline. – jalf

1

Ein anderer Mitarbeiter wies darauf hin, dass es eine Möglichkeit sein könnte, Funktorobjekte als Funktionen zu tarnen. Zum Beispiel diese:

my_functor(); 

Ist wirklich:

my_functor.operator()(); 

Also das heißt das:

my_functor(int n, float f){ ... }; 

Kann verwendet werden, dies auch zu überlasten?

my_functor.operator()(int n, float f){ ... }; 
+0

Ihre letzte Zeile ist überhaupt keine Überlastung des Operators. Es muss sein: ".operator() (int n, float f)", das beim ersten Mal sehr verwirrend aussieht. Sie können diesen "Funktionsaufruf-Operator" wie andere Funktionen überladen, aber Sie können ihn nicht mit der von Ihnen angegebenen Nicht-Operator-Überladung überlasten. – altruic

+0

@altruic, guter Punkt. Habe es einfach repariert. – JeffV

+0

Ihre zweite Zeile ist falsch, es ist tatsächlich "my_functor.operator()();". my_functor.operator() ist die Methodenreferenz, während die zweite Menge von() den Aufruf bezeichnet. – eduffy

2

Sie können auch über die C++ faq's Matrix example schauen. Es gibt gute Möglichkeiten, es zu tun, aber es hängt natürlich davon ab, was Sie erreichen möchten.

4

Ein Funktor ist keine Funktion, Sie können ihn also nicht überladen.
Ihr Mitarbeiter ist zwar korrekt, dass das Überladen von operator() verwendet wird, um "Funktoren" zu erstellen - Objekte, die wie Funktionen aufgerufen werden können. In Kombination mit Templates, die "funktionsähnliche" Argumente erwarten, kann dies sehr mächtig sein, da die Unterscheidung zwischen einem Objekt und einer Funktion unscharf wird.

Wie andere Poster gesagt haben: Funktoren haben einen Vorteil gegenüber einfachen Funktionen, in denen sie Zustand haben können. Dieser Status kann über eine einzelne Iteration (z. B. zum Berechnen der Summe aller Elemente in einem Container) oder über mehrere Iterationen (z. B. zum Auffinden aller Elemente in mehreren Containern, die bestimmte Kriterien erfüllen) verwendet werden.

18

Es ermöglicht einer Klasse, sich wie eine Funktion zu verhalten. Ich habe es in einer Protokollierungsklasse verwendet, wo der Anruf eine Funktion sein sollte, aber ich wollte den zusätzlichen Vorteil der Klasse.

so etwas wie folgt aus:

logger.log("Log this message"); 

verwandelt sich in dieses:

logger("Log this message"); 
3

starten std::for_each verwenden, std::find_if usw. häufiger in Ihrem Code und Sie werden sehen, warum es praktisch ist zu haben die Fähigkeit, den Operator() zu überladen. Außerdem können Funktoren und Aufgaben eine klare Aufrufmethode verwenden, die nicht mit den Namen anderer Methoden in den abgeleiteten Klassen in Konflikt steht.

2

Funktoren sind grundsätzlich wie Funktionszeiger. Sie sollen in der Regel kopierbar sein (wie Funktionszeiger) und wie Funktionszeiger aufgerufen werden. Der Hauptvorteil besteht darin, dass der Funktionsaufruf von operator() inline ausgeführt werden kann, wenn Sie über einen Algorithmus verfügen, der mit einem Templat-Funktor arbeitet. Funktionszeiger sind jedoch immer noch gültige Funktoren.

5

Viele haben geantwortet, dass es einen Funktor macht, ohne einen wichtigen Grund zu nennen, warum ein Funktor besser ist als eine einfache alte Funktion.

Die Antwort ist, dass ein Funktor Zustand haben kann. Betrachten Sie eine Summierungsfunktion - sie muss eine laufende Summe behalten.

class Sum 
{ 
public: 
    Sum() : m_total(0) 
    { 
    } 
    void operator()(int value) 
    { 
     m_total += value; 
    } 
    int m_total; 
}; 
+0

Das erklärt nicht, warum es notwendig ist, diese Tatsache zu verbergen, dass es ein Objekt ist und es als eine Funktion tarnt. – JeffV

+5

Jeff V: Bequemlichkeit. Es bedeutet, dass die gleiche Syntax verwendet werden kann, um den Aufruf auszuführen, unabhängig davon, ob wir einen Funktor oder einen Funktionszeiger aufrufen. Wenn Sie beispielsweise std :: for_each betrachten, funktioniert dies entweder mit Funktoren oder Funktionszeigern, weil in beiden Fällen die Syntax für den Aufruf dieselbe ist. – jalf

2

Eine Stärke, die ich sehen kann, aber das kann diskutiert werden, ist, dass die Signatur von operator() über verschiedene Typen hinweg aussieht und sich verhält. Wenn wir einen Klassenreporter mit einem Membermethodenreport (..) und dann einen anderen Klassenwriter hätten, der eine Membermethode write (..) hätte, müssten wir Adapter schreiben, wenn wir beide Klassen als vielleicht verwenden möchten eine Vorlagenkomponente eines anderen Systems. Alles, was es interessiert, ist, Streicher weiterzugeben oder was hast du? Ohne den Einsatz von operator() Überlastung oder spezielle Art Adapter zu schreiben, könnten Sie nicht Sachen wie

T t; 
t.write("Hello world"); 

tun, weil T eine Anforderung hat, dass es eine Memberfunktion namens schreiben, die etwas implizit gießbaren zu const char akzeptiert * (oder besser const char []). Die Reporter-Klasse in diesem Beispiel hat das nicht, daher könnte T (ein Vorlagenparameter), der Reporter ist, nicht kompilieren.

Doch wie weit kann ich sehen, diese mit verschiedenen Arten funktionieren würde

T t; 
t("Hello world"); 

es allerdings nach wie vor verlangt ausdrücklich, dass der Typ T hat ein solcher Operator definiert, so dass wir immer noch eine Anforderung an T. persönlich Ich denke nicht, dass es mit Funktoren zu seltsam ist, da sie häufig verwendet werden, aber ich würde lieber andere Mechanismen für dieses Verhalten sehen. In Sprachen wie C# könnte man einfach einen Delegierten übergeben. Ich kenne mich mit Memberfunktionszeigern in C++ nicht aus, aber ich könnte mir vorstellen, dass Sie dort auch dasselbe Verhalten erreichen könnten.

Anders als das syntaktische Zuckerverhalten sehe ich nicht wirklich die Stärken der Überlastung des Operators, um solche Aufgaben zu erfüllen.

Ich bin sicher, es gibt wissentlichere Leute, die bessere Gründe haben als ich, aber ich dachte, ich würde meine Meinung für den Rest von euch austeilen.

+2

Der Vorteil der Verwendung des Operators() besteht darin, dass Ihr Vorlagenparameter gleichermaßen ein Funktionszeiger oder ein Funktor sein kann. –

1

Andere Beiträge haben einen guten Job gemacht und beschrieben, wie operator() funktioniert und warum es nützlich sein kann.

Ich habe vor kurzem einen Code verwendet, der Operator() sehr umfangreich verwendet. Ein Nachteil des Überladens dieses Operators ist, dass einige IDEs dadurch zu weniger effektiven Werkzeugen werden. In Visual Studio können Sie normalerweise mit der rechten Maustaste auf einen Methodenaufruf klicken, um zur Methodendefinition und/oder Deklaration zu wechseln. Leider ist VS nicht schlau genug, um Aufrufe von operator() zu indexieren. Vor allem in komplexem Code mit überschriebenen operator() - Definitionen überall kann es sehr schwierig sein herauszufinden, welcher Codeabschnitt wo ausgeführt wird. In einigen Fällen musste ich den Code ausführen und durchforsten, um herauszufinden, was tatsächlich lief.

Verwandte Themen