Ergebnis 1 bis 3 von 3

[C++] Frage zu std::function

  1. #1 Zitieren
    Ritter Avatar von Feuerstern
    Registriert seit
    Sep 2007
    Beiträge
    1.789
    Hallo Leute,
    Ich probiere gerade etwas mit std::function herum und habe im Internet gesehen das die Funktionen manchmal mit dem "&" Operator zugewiesen werden und manchmal ohne. Leider konnte ich bisher nicht herausfinden wo der Unterschied liegt. Das Ergebnis ist beim Aufruf in beiden Fällen das gleiche:
    Code:
    #include <iostream>
    #include <functional>
    
    using namespace std;
    
    void printText()
    {
        cout << "printText" << endl;
    }
    
    int main()
    {
        std::function<void()> f_printText1 = printText;
        std::function<void()> f_printText2 = &printText;
    
        f_printText1();
        f_printText2();
       
        return 0;
    }
    Des Weiteren sieht man oft, dass statt die Funktion direkt zuzuweißen, std::bind benutzt wird. Ich weiß das man damit direkt feste Werte für die Funktionsparameter vergeben kann, so das man diese beim Aufruf der std::function Variable nicht mehr angeben muss, aber hat das nutzen von std::bind auch Vorteile wenn die Funktion gar keine Parameter hat oder diese sowieso beim Aufruf mit gegben werden sollen?
    Feuerstern ist offline

  2. #2 Zitieren
    Legende Avatar von jabu
    Registriert seit
    Jul 2011
    Beiträge
    7.322
    Zitat Zitat von Feuerstern Beitrag anzeigen
    Hallo Leute,
    Ich probiere gerade etwas mit std::function herum und habe im Internet gesehen das die Funktionen manchmal mit dem "&" Operator zugewiesen werden und manchmal ohne. Leider konnte ich bisher nicht herausfinden wo der Unterschied liegt. Das Ergebnis ist beim Aufruf in beiden Fällen das gleiche:
    Code:
    #include <iostream>
    #include <functional>
    
    using namespace std;
    
    void printText()
    {
        cout << "printText" << endl;
    }
    
    int main()
    {
        std::function<void()> f_printText1 = printText;
        std::function<void()> f_printText2 = &printText;
    
        f_printText1();
        f_printText2();
       
        return 0;
    }
    Da ich nicht weiß, ob es noch von Interesse ist, kurz und knapp:

    In beiden Fällen wird mit Funktionszeigern initialisiert, weil Funktionsnamen bei Zuweisung, Initialisierung sowie bei Parameterübergabe zu den Zeigertypen dieser Funktionen zerfallen. Deswegen gibt es tatsächlich keinen Unterschied, sodass der Adressoperator hier überflüssig ist. Es resultiert absolut kein Unterschied zur Laufzeit.

    Zeiger auf Memberfunktionen erfordern jedoch stets den Adressoperator (obwohl der GNU-Compiler ohne auskommt und damit den Standard verletzt). Sie sind mit Zeigern auf freie Funktionen inkompatibel. Ausgenommen davon sind Zeiger auf statische Memberfunktionen, welche sich wie solche auf freie Funktionen verhalten und zu ihnen kompatibel sind, denn statische Memberfunktionen haben statische Lebensdauer und sind nicht an eine bestimmte Objektinstanz gebunden, weshalb ein eventuell benötigtes Berechnen der endgültigen Adresse (über einen Offset, welchen ein Zeiger auch eine echte Memberfunktion symbolisiert) entfällt, sodass die Adresse der aufzurufenden Funktion, ganz wie bei freien Funktionen, direkt eingesetzt werden kann.

    Trotzdem gibt es einen grundsätzlichen Unterschied zwischen den beiden rechtsseitigen Ausdrücken, was der Operator sizeof veranschaulicht:
    Code:
    sizeof(&printText); // ergibt Anzahl an Bytes, welche der Funktionszeiger belegt
    sizeof(printText);  // Fehler, kompiliert nicht
    1. Fall:
    Ein Funktionszeiger, welcher sich aus dem Adressoperator ergibt, ist von einem Zeigertyp, welcher eine bestimmte Anzahl an Bytes belegt, z.B. auf x86-Systemen 4 Byte, was im Maschinencode der natürlichen Breite eines Maschinenwortes entspricht, welches die Adresse beinhaltet.

    2. Fall:
    Der Funktionsname selbst bezeichnet weder einen Typ noch ein Objekt, weshalb sich definitionsgemäß keine Größe bestimmen lässt, was zum Abbruch der Übersetzung führt. Er zerfällt nicht zu einem Zeiger, weil es weder zu einer Wertzuweisung noch zu einer Parameterübergabe kommt. Zur Erinnerung: sizeof ist ein Operator und keine Funktion.

    Wenn man die automatische Generierung über ein Template aus deinem Beispiel wegnimmt und direkt einen Funktionszeiger initialisiert, erhält man:
    Code:
    // Funktionszeiger explizit
    void (*functionPtr1)() = &printText;
    void (*functionPtr2)() = printText;
    
    // Funktionszeiger per automatischer Typdeduktion (seit C++11)
    auto functionPtr3 = &printText;
    auto functionPtr4 = printText; // einfacher geht es nicht
    
    // Funktionszeiger mit Typalias per typedef (von Anfang an enthalten),
    // was praktisch ist, wenn man den expliziten Typ mehrmals braucht
    typedef void(*functionPtr_T1)();
    functionPtr_T1 functionPtr5 = &printText;
    functionPtr_T1 functionPtr6 = printText;
    
    // dasselbe wie zuvor, jedoch mit Typalias per using (seit C++11),
    // was für manche weniger verwirrend ist
    using functionPtr_T2 = void(*)();
    functionPtr_T2 functionPtr7 = &printText;
    functionPtr_T2 functionPtr8 = printText;
    
    // Aufruf (immer gleiche Ergebnisse)
    functionPtr1();
    functionPtr2();
    functionPtr3();
    functionPtr4();
    functionPtr5();
    functionPtr6();
    functionPtr7();
    functionPtr8();
    Natürlich geht das entsprechend auch mit Funktionsparametern, auf die wegen der Übersichtlichkeit verzichtet wurde.

    Ein Beispiel mit einer Memberfunktion und einem Parameter:
    Code:
    #include <functional>
    #include <iostream>
    
    struct Foo
    {
        void memberFunc(const char *szText) const
        {
            std::cout <<  szText << std::endl;
        }
    };
    
    int main()
    {
        Foo foo;
    
        // der normale Weg
        void (Foo::*memberFunctionPtr)(const char *) const = &Foo::memberFunc;
        (foo.*memberFunctionPtr)("blah1");
    
        // weniger sperrig beim Aufruf
        std::function<void(Foo &, const char *)> fobjMemberFunc = &Foo::memberFunc;
        fobjMemberFunc(foo, "blah2");
    
        std::cout << std::endl;
        std::cin.get();
    
        return 0;
    }

    Wenn man größtenteils objektorientiert programmiert, gibt es kaum einen vernünftigen Grund, das funktionale Paradigma ohne konkreten Anlass, z.B. wegen einer benötigten Bibliotheksfunktion, einzustreuen. Grundsätzlich führt eine Vermengung zu schwerer lesbarem, schwerer wartbarem und fehlerträchtigerem Code, während Konsistenz, also das Durchziehen entweder des objektorientierten oder des funktionalen Paradigmas, Lesbarkeit, Wartbarkeit und Fehlerarmut befördert. Zudem kompiliert Code, welcher keine mächtigen Templates benötigt, deutlich schneller. Nicht jedes neue Feature (hier aus C++11) ist immer sinnvoll. Es kommt wohl auf den jeweiligen Kontext an.

    Des Weiteren sieht man oft, dass statt die Funktion direkt zuzuweißen, std::bind benutzt wird. Ich weiß das man damit direkt feste Werte für die Funktionsparameter vergeben kann, so das man diese beim Aufruf der std::function Variable nicht mehr angeben muss, aber hat das nutzen von std::bind auch Vorteile wenn die Funktion gar keine Parameter hat oder diese sowieso beim Aufruf mit gegben werden sollen?
    Die sehe ich ganz überwiegend darin, ein funktionales Paradigma konsequent durchziehen zu können, aus den o.g. Gründen, wobei C++ nicht die Sprache der Wahl für funktionale Programmierung ist, weshalb ich ihren Anwendungsbereich eher innerhalb kleiner in sich abgeschlossener Einheiten sehe.
    jabu ist offline Geändert von jabu (11.10.2016 um 15:28 Uhr) Grund: schließende spitze Klammer fehlte

  3. #3 Zitieren
    Ritter Avatar von Feuerstern
    Registriert seit
    Sep 2007
    Beiträge
    1.789
    Hallo jabu,
    danke für deine ausführliche Antwort. Die Sache ist noch aktuell für mich deshalb war das sehr hilfreich.

    Grüße
    Feuerstern ist offline

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •