Was sind C ++ - Funktoren und ihre Verwendung?


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?


4
Dieses Thema wurde als Antwort auf diese Frage behandelt: stackoverflow.com/questions/317450/why-override-operator#317528
Luc Touraille

2
Es wird verwendet, um einen Abschluss in C ++ zu erstellen.
Kupfer.hat

Wenn sich jemand die Antworten unten ansieht, fragt er sich, was dies operator()(...)bedeutet: Der Operator "Funktionsaufruf" wird überlastet . Es ist einfach eine Überlastung des ()Bedieners für den Bediener. Verwechseln Sie nicht den operator()Aufruf einer aufgerufenen Funktion operator, sondern betrachten Sie sie als die übliche Syntax zum Überladen von Operatoren.
Zardosht

Antworten:


1041

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":

// this is a functor
struct add_x {
  add_x(int val) : x(val) {}  // Constructor
  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. 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::transformaufgerufen werden soll. Es sollte anrufen add_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.


32
Können Sie diese Zeile erklären, bitte std :: transform (in.begin (), in.end (), out.begin (), add_x (1)); warum schreibst du dort add_x, nicht das add42?
Alecs

102
@Alecs Beide hätten funktioniert (aber der Effekt wäre anders gewesen). Wenn ich verwendet hätte add42, hätte ich den zuvor erstellten Funktor verwendet und jedem Wert 42 hinzugefügt. Mit add_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.
Jalf

8
@ Zadane natürlich. Sie müssen nur das haben 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.
Jalf

4
@ rikimaru2013 Im Sprachgebrauch der funktionalen Programmierung haben Sie Recht, eine Funktion ist auch ein Funktor, aber im Sprachgebrauch von C ++ ist der Funktor speziell eine Klasse, die als Funktion verwendet wird. Die Terminologie wurde schon früh etwas missbraucht, aber die Unterteilung ist eine nützliche Unterscheidung und bleibt daher bis heute bestehen. Wenn Sie in einem C ++ - Kontext Funktionen als "Funktoren" bezeichnen, werden Sie die Konversation nur verwirren.
srm

6
Ist es eine Klasse oder eine Instanz der Klasse? In den meisten Quellen add42würde man einen Funktor nennen, nicht add_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?
Sergei Tachenov

121

Kleine Ergänzung. Mit folgenden Funktionen können Sie boost::functionFunktoren aus folgenden Funktionen und Methoden erstellen:

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 Status 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 ist, dass Sie mit boost :: bind und boost :: function einen Funktor aus der Klassenmethode erstellen können. 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 ein Problem mit all dem Zeug, Compiler-Fehlermeldungen sind nicht von Menschen lesbar :)


4
Sollte operator ()in Ihrem ersten Beispiel nicht öffentlich sein, da Klassen standardmäßig privat sind?
NathanOliver

4
Vielleicht verdient diese Antwort irgendwann ein Update, da Lambdas jetzt der einfachste Weg sind, einen Funktor von
irgendetwas

102

Ein Functor ist ein Objekt, das wie eine Funktion wirkt. Grundsätzlich eine Klasse, die definiert operator().

class MyFunctor
{
   public:
     int operator()(int x) { return x * 2;}
}

MyFunctor doubler;
int x = doubler(5);

Der wahre Vorteil ist, dass ein Funktor den 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)
{ ....}

11
Sie müssen nur hinzufügen, dass sie wie ein Funktionszeiger verwendet werden können.
Martin York

7
@LokiAstari - Für diejenigen, die neu im Konzept sind, könnte das etwas irreführend sein. Funktoren können "wie" verwendet werden, aber nicht immer "anstelle von" Funktionszeigern. Beispielsweise kann eine Funktion, die einen Funktionszeiger verwendet, einen Funktor nicht an seine Stelle setzen, selbst wenn der Funktor dieselben Argumente und Rückgabewerte wie der Funktionszeiger hat. Aber im Großen und Ganzen sind Funktoren beim Entwerfen der bevorzugte und theoretisch "modernere" Weg.
MasonWinsauer

Warum kehrt der zweite zurück, intwenn er zurückkehren sollte bool? Dies ist C ++, nicht C. Als diese Antwort geschrieben wurde, gab es sie boolnicht?
Fund Monica Klage

@QPaysTaxes Ein Tippfehler, denke ich. Ich habe wahrscheinlich den Code aus dem ersten Beispiel kopiert und vergessen, ihn zu ändern. Ich habe es jetzt behoben.
James Curran

1
@Riasat Wenn sich Matcher in einer Bibliothek befindet, ist die Definition von Is5 () recht einfach. Und Sie können Is7 (), Is32 () usw. erstellen. Außerdem ist dies nur ein Beispiel. Der Funktor könnte viel komplizierter sein.
James Curran

51

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:

  • Funktionsobjekt kann Status haben
  • Das 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:

  • Funktionsobjekt kann häufig inline sein

Nachteile:

  • Das Funktionsobjekt kann zur Laufzeit nicht mit einem anderen Funktionsobjekttyp ausgetauscht werden (zumindest, wenn es nicht eine Basisklasse erweitert, was zu einem gewissen Overhead führt).

Wird anstelle der virtuellen Funktion verwendet:

Eigenschaften:

  • Ein Funktionsobjekt (nicht virtuell) erfordert kein vtable- und Laufzeit-Dispatching und ist daher in den meisten Fällen effizienter

Nachteile:

  • Das Funktionsobjekt kann zur Laufzeit nicht mit einem anderen Funktionsobjekttyp ausgetauscht werden (zumindest, wenn es nicht eine Basisklasse erweitert, was zu einem gewissen Overhead führt).

1
Können Sie diesen Anwendungsfall in einem realen Beispiel erklären? Wie können wir Funktoren als Polymorphismus- und Funktionszeiger verwenden?
Milad Khajavi

1
Was bedeutet eigentlich, dass ein Funktor den Status hält?
Erogol

Vielen Dank für den Hinweis, dass man eine Basisklasse braucht, um eine Art Polymorphismus zu haben. Ich habe nur das Problem, dass ich einen Funktor an der gleichen Stelle wie einen einfachen Funktionszeiger verwenden muss und die einzige Möglichkeit, die ich gefunden habe, darin bestand, eine Funktor-Basisklasse zu schreiben (da ich kein C ++ 11-Zeug verwenden kann). Ich war mir nicht sicher, ob dieser Aufwand Sinn macht, bis ich Ihre Antwort gelesen habe.
idclev 463035818

1
@Erogol Ein Funktor ist ein Objekt, das die Syntax unterstützt foo(arguments). Daher kann es Variablen enthalten; Wenn Sie beispielsweise eine update_password(string)Funktion hatten, möchten Sie möglicherweise nachverfolgen, wie oft dies geschehen ist. Mit einem Funktor kann dies private long timeden 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.l
Fund Monica's Lawsuit

4
⁺¹ für die Erwähnung, dass der Name ohne Grund erfunden wurde. Ich habe gerade nach der Beziehung zwischen dem mathematischen (oder funktionalen, wenn Sie wollen) Funktor und dem aus C ++ gesucht .
Hi-Angel

41

Wie 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 MultiplyByFunktor definieren , 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 ein MultiplyByObjekt 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 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 transformsei denn, dieser Aufruf wurde eingebunden und der Compiler weiß, dass Sie ihm immer dieselbe Funktion übergeben.


37

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 + operatorkö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, () operatorwas 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 () operatorist 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)

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 ungenau oder einfach falsch ist, können Sie mich gerne korrigieren!


1
Der Operator () wird als Funktionsaufrufoperator bezeichnet. Ich denke, man könnte es auch den Klammeroperator nennen.
Gautam

4
"Dieser Parameter ist eigentlich das Argument" parameterVar ", das von dem Konstruktor übergeben wurde, den wir gerade geschrieben haben." Huh?
Leichtigkeitsrennen im Orbit

22

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:

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 nimmt a std::vector<T>und gibt zurück, std::vector<U>wenn eine Funktion gegeben ist F, die a nimmt Tund a zurückgibt U. Ein Funktor muss nicht über Containertypen definiert werden, sondern kann auch für jeden Schablonentyp 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 a konvertiert double:

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 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 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 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 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));

2
Artikel, in dem argumentiert wird, dass functor für diese Bedeutung korrekt verwendet werden sollte (siehe auch en.wikipedia.org/wiki/Functor ) und dass die Verwendung für Funktionsobjekte nur schlampig ist: jackieokay.com/2017/01/26/functors.html It Dafür kann es jedoch zu spät sein, wenn man bedenkt, wie viele Antworten hier nur die Bedeutung des Funktionsobjekts berücksichtigen.
Armb

2
Diese Antwort sollte die mit> 700 Upvotes sein. Als jemand, der Haskell besser kennt als C ++, hat mich die C ++ - Verkehrssprache die ganze Zeit verwirrt.
Mschmidt

Kategorietheorie und C ++? Ist das Bartosz Milewskis geheimer SO-Account?
Mateen Ulhaq

1
Es kann hilfreich sein, die Funktorgesetze in Standardnotation zusammenzufassen: fmap(id, x) = id(x)und fmap(f ◦ g, x) = fmap(f, fmap(g, x)).
Mateen Ulhaq

@mschmidt während functor dies auch bedeutet, überlädt C ++ den Namen, um dasselbe wie "Funktionsobjekt" zu bedeuten
Caleth

9

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.


3
Wenn Ihr Funktor verschiedene spezifische Funktionen aufgerufen hat und diese anderen Funktionen sich in der Anzahl der von ihnen akzeptierten Parameter unterschieden, bedeutet dies, dass Ihr Funktor eine variable Anzahl von Argumenten für den Versand an diese anderen Funktionen akzeptiert hat?
Johnbakers

4
Können Sie bitte das obige Szenario erklären, indem Sie einen Teil des Codes zitieren
?

3

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 .


Dies (das Matrixbeispiel) ist eine einfache Verwendung, operator()jedoch keine Verwendung von Funktionsobjekteigenschaften.
Renardesque

3

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:

/* prints "this is a very simple and poorly used task queue" */
class Functor
{
public:
    std::string output;
    Functor(const std::string& out): output(out){}
    operator()() const
    {
        std::cout << output << " ";
    }
};

int main(int argc, char **argv)
{
    std::list<Functor> taskQueue;
    taskQueue.push_back(Functor("this"));
    taskQueue.push_back(Functor("is a"));
    taskQueue.push_back(Functor("very simple"));
    taskQueue.push_back(Functor("and poorly used"));
    taskQueue.push_back(Functor("task queue"));
    for(std::list<Functor>::iterator it = taskQueue.begin();
        it != taskQueue.end(); ++it)
    {
        *it();
    }
    return 0;
}

/* prints the value stored in "i", then asks you if you want to increment it */
int i;
bool should_increment;
int doSomeWork()
{
    std::cout << "i = " << i << std::endl;
    std::cout << "increment? (enter the number 1 to increment, 0 otherwise" << std::endl;
    std::cin >> should_increment;
    return 2;
}
void doSensitiveWork()
{
     ++i;
     should_increment = false;
}
class BaseCoroutine
{
public:
    BaseCoroutine(int stat): status(stat), waiting(false){}
    void operator()(){ status = perform(); }
    int getStatus() const { return status; }
protected:
    int status;
    bool waiting;
    virtual int perform() = 0;
    bool await_status(BaseCoroutine& other, int stat, int change)
    {
        if(!waiting)
        {
            waiting = true;
        }
        if(other.getStatus() == stat)
        {
            status = change;
            waiting = false;
        }
        return !waiting;
    }
}

class MyCoroutine1: public BaseCoroutine
{
public:
    MyCoroutine1(BaseCoroutine& other): BaseCoroutine(1), partner(other){}
protected:
    BaseCoroutine& partner;
    virtual int perform()
    {
        if(getStatus() == 1)
            return doSomeWork();
        if(getStatus() == 2)
        {
            if(await_status(partner, 1))
                return 1;
            else if(i == 100)
                return 0;
            else
                return 2;
        }
    }
};

class MyCoroutine2: public BaseCoroutine
{
public:
    MyCoroutine2(bool& work_signal): BaseCoroutine(1), ready(work_signal) {}
protected:
    bool& work_signal;
    virtual int perform()
    {
        if(i == 100)
            return 0;
        if(work_signal)
        {
            doSensitiveWork();
            return 2;
        }
        return 1;
    }
};

int main()
{
     std::list<BaseCoroutine* > coroutineList;
     MyCoroutine2 *incrementer = new MyCoroutine2(should_increment);
     MyCoroutine1 *printer = new MyCoroutine1(incrementer);

     while(coroutineList.size())
     {
         for(std::list<BaseCoroutine *>::iterator it = coroutineList.begin();
             it != coroutineList.end(); ++it)
         {
             *it();
             if(*it.getStatus() == 0)
             {
                 coroutineList.erase(it);
             }
         }
     }
     delete printer;
     delete incrementer;
     return 0;
}

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.


2

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:

void* method(void* something)

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 ThreadKlasse zu erstellen . Wenn Sie Methoden aus der MyClassKlasse ausführen wollten , wandelte ich diese Methoden in Functorabgeleitete Klassen um.

Die ThreadKlasse 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 was startThread(..)in arg empfangen werden sollte, ist ein void*umwandelnder Verweis auf eine Instanz im Heap einer Functorabgeleiteten Klasse, die Functor*bei der Ausführung zurückgesetzt und dann als run()Methode bezeichnet wird.


2

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:

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 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.

// 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 obige 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 die Parameter zu binden, müssen wir nun 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 zur Verwendung der Klasse binder3 - 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. Verwenden Sie das folgende 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 nennen Sie es:

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

Anmerkung: f3 (); ruft die Methode task1-> ThreeParameterTask (21,22,23) auf;.

Den vollständigen Kontext dieses Musters finden Sie unter folgendem Link


2

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.

#include <string>
#include <vector>
#include <algorithm>

template <typename T>
T min3(const T& a, const T& b, const T& c)
{
   return std::min(std::min(a, b), c);
}

class levenshtein_distance 
{
    mutable std::vector<std::vector<unsigned int> > matrix_;

public:
    explicit levenshtein_distance(size_t initial_size = 8)
        : matrix_(initial_size, std::vector<unsigned int>(initial_size))
    {
    }

    unsigned int operator()(const std::string& s, const std::string& t) const
    {
        const size_t m = s.size();
        const size_t n = t.size();
        // The distance between a string and the empty string is the string's length
        if (m == 0) {
            return n;
        }
        if (n == 0) {
            return m;
        }
        // Size the matrix as necessary
        if (matrix_.size() < m + 1) {
            matrix_.resize(m + 1, matrix_[0]);
        }
        if (matrix_[0].size() < n + 1) {
            for (auto& mat : matrix_) {
                mat.resize(n + 1);
            }
        }
        // The top row and left column are prefixes that can be reached by
        // insertions and deletions alone
        unsigned int i, j;
        for (i = 1;  i <= m; ++i) {
            matrix_[i][0] = i;
        }
        for (j = 1; j <= n; ++j) {
            matrix_[0][j] = j;
        }
        // Fill in the rest of the matrix
        for (j = 1; j <= n; ++j) {
            for (i = 1; i <= m; ++i) {
                unsigned int substitution_cost = s[i - 1] == t[j - 1] ? 0 : 1;
                matrix_[i][j] =
                    min3(matrix_[i - 1][j] + 1,                 // Deletion
                    matrix_[i][j - 1] + 1,                      // Insertion
                    matrix_[i - 1][j - 1] + substitution_cost); // Substitution
            }
        }
        return matrix_[m][n];
    }
};

1

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.


-10

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 ;-)


Warum beschreiben Sie einen Funktor als "Methode ohne Namen"?
Anderson Green

5
Eine Funktion ohne Namen wird als Lambda bezeichnet.
Paul Fultz II
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.