STL: Sinn von Funktionsobjekten und -adaptern?



  • Hallo,

    ich lese mich gerade in die STL ein und verstehe den Sinn von Funktionsobjekten noch nicht so ganz. Zum Beispiel hier:

    template <class T>
    class eq : public binary_function<T, T, bool> {
    public:
        bool operator() (const T &a, const T& b) const {
            return a == b;
        }
    };
    

    Toll, jetzt habe ich ein Funktionsobjekt, dass a == b zurückgibt. Aber für das ganze brauche ich diese binary_function -Klasse doch nicht.

    Das hier erfüllt doch genau den gleichen Zweck:

    template <class T>
    bool eq(const T &a, const T &b) {
        return a == b;
    }
    

    Dazu kommt noch, dass ich den Aufruf von eq<int>(10, 20) etwas schöner finde als eq<int>() (10, 20); .

    Aber das ganz wird ja noch schlimmer: Diese Arithmetik-, Vergleichs- und Logik-Funktoren scheinen mir gerade mehr als überflüssig.

    Anstatt einfach i < 10 zu schreiben, lieber less<int>() (i, 10) ? Den einzigen "Vorteil", den ich hier sehe ist die Typenüberprüfung - wobei das ja auch eher ein Nachteil werden. Wenn ich keine int's mehr vergleichen will, sondern 2 long-Werte habe, muss ich das int im Code ja auch umändern.

    Ich verstehe den Sinn einfach nicht, wäre nett, wenn mir das jemand erklären könnte.

    Grüße,
    Amgon 🙂


  • Mod

    Lies dir am besten mal den Wikipedia Artikel zu dem Thema durch:
    http://en.wikipedia.org/wiki/Function_object
    Dieser dürfte schonmal viele Fragen beantworten, wenn noch was unklar ist, frag weiter.

    Die ganzen templates für Operatorvergleiche sind natürlich dafür da, dass du nicht für jeden Typen eine eigene Funktion schreiben musst. Und du brauchst Funktionen, denn einen Operator alleine kannst du nicht an eine andere Funktion übergeben.



  • Amgon schrieb:

    Das hier erfüllt doch genau den gleichen Zweck:

    template <class T>
    bool eq(const T &a, const T &b) {
        return a == b;
    }
    

    Hier vielleicht schon, aber Funktoren sind viel flexibler und mächtiger als Funktionszeiger. Die Klassentemplates std::unary_function und std::binary_function stellen gleich Typedefs bereit, die zum Beispiel von std::bind1st() verwendet werden. Zudem ist die Chance bei Funktoren grundsätzlich grösser, dass Aufrufe vom Compiler inline gemacht werden können. Funktionszeiger sind in dieser Hinsicht weniger transparent (moderne Compiler sollten Optimierungen bei ihnen trotzdem einigermassen im Griff haben). Ein anderer Vorteil ergibt sich bei Containern wie std::set oder std::map - ein Funktor kann einmal als Templateparameter angegeben werden, bei Funktionszeigern hingegen muss man zuerst den Typ deklarieren und im Konstruktor erst die eigentliche Funktion übergeben.

    Ein weiteres Beispiel für die Möglichkeiten, die Funktionsobjekte bieten: Man kann dem Konstruktor Argumente übergeben, welche das Objekt beim eigentlichen Aufruf berücksichtigt - das geht bei Funktionen nicht direkt. So kannst du mit der gleichen Klasse verschiedene Aktionen durchführen. Das folgende Beispiel sollte das Prinzip ungefähr verdeutlichen:

    struct linear_op
    {
        linear_op(int factor, int increment)
        : fac(factor)
        , inc(increment)
        {
        }
    
        void operator() (int& number) const
        {
            number = fac * number + inc;
        }
    
        int inc;
        int fac;
    };
    
    std::vector<int> numbers; // zu füllen
    std::for_each(numbers.begin(), numbers.end(), linear_op(3, 2));
    std::for_each(numbers.begin(), numbers.end(), linear_op(0, 1));
    

    Amgon schrieb:

    Dazu kommt noch, dass ich den Aufruf von eq<int>(10, 20) etwas schöner finde als eq<int>() (10, 20); .

    Wann musst du denn ein temporäres Objekt erstellen und dieses im gleichen Ausdruck aufrufen? Aufzurufen brauchst du nichts, wenn du Funktionszeiger/-objekte an die High-Order-STL-Algorithmen übergibst. Die sind für den Aufruf zuständig.

    Amgon schrieb:

    Aber das ganz wird ja noch schlimmer: Diese Arithmetik-, Vergleichs- und Logik-Funktoren scheinen mir gerade mehr als überflüssig.

    Anstatt einfach i < 10 zu schreiben, lieber less<int>() (i, 10) ? Den einzigen "Vorteil", den ich hier sehe ist die Typenüberprüfung - wobei das ja auch eher ein Nachteil werden. Wenn ich keine int's mehr vergleichen will, sondern 2 long-Werte habe, muss ich das int im Code ja auch umändern.

    Ein std::set<MyClass, std::greater<MyClass> > ist unheimlich praktisch, weil man nur durch das greater angibt, dass Objekte absteigen sortiert werden sollen.

    Beispiel für einen arithmetischen Funktionsadapter, der zwei Arrays elementweise multipliziert und das Ergebnis in einem dritten speichert:

    int a[] = {1, 2, 3};
    int b[] = {4, 5, 6};
    int products[3];
    std::transform(a, a+3, b, products, std::multiplies<int>());
    

    Wie du siehst, können Funktoren einiges vereinfachen. Besonders wenn man sich weiter in Richtung funktionale Programmierung bewegt, hat man ziemlich mächtige Werkzeuge zur Verfügung - mit C++0x erst recht. Ich rate dir, den Artikel des Magazins zu lesen. Auch Teil 1 und 2 sind sehr empfehlenswert.



  • Achso, diese Funktoren machen also erst später, in Kombination mit anderen STL-Komponenten, sinn? Klingt logisch, dankeschön für die Antworten. 🙂

    Dann lese ich einfach mal weiter.



  • Amgon schrieb:

    Achso, diese Funktoren machen also erst später, in Kombination mit anderen STL-Komponenten, sinn?

    Ja, vor allem im Zusammenhang mit den Containern, Iteratoren und Algorithmen (welche grösstenteils auf Containern operieren) zieht man grossen Nutzen aus den STL-Hilfsklassen. Man kann diese Konzepte kombinieren, wie in meinem Beispiel mit std::for_each() und std::vector zu sehen ist.

    In den erwähnten Foren-Artikeln findest eine gute Einführung in die STL, betrachte diese als Ergänzung zu deinem Buch. Schau dir unbedingt auch das Nachschlagewerk www.cplusplus.com an, dort ist die STL recht gut dokumentiert. In meinem letzten Post siehst du ausserdem einige Anwendungsbeispiele, die dir einen Eindruck vermitteln können.



  • Und da dein Buch vmtl. "C++ von A bis Z" heißt, solltest du gerade bei diesem Thema ein paar schönere Bücher bevorzugen. 🙂



  • Ad aCTa schrieb:

    Und da dein Buch vmtl. "C++ von A bis Z" heißt, solltest du gerade bei diesem Thema ein paar schönere Bücher bevorzugen. 🙂

    Volltreffer, gibts da dran etwas auszusetzen? 🙂


Anmelden zum Antworten