wieso ist ++i schneller als i++, was ist der unterschied, wie überlade ich sie



  • Erstmal ein wenig Grundlegendes zum ++i und i++ aus
    Inkrement- und Dekrement-Operatoren

    Inkrement- und Dekrement-Operatoren
    Bei den Operatoren ++ und -- handelt es sich um spezielle Zuweisungsoperatoren. Es gibt sie jeweils in einer Präfix- und einer Postfix-Version. Alleinstehend entsprechen beide Versionen += 1 bzw. -= 1:
    ++a; und a++; entsprechen beide a += 1;

    Die Operatoren sind auf ganzzahlige und Pointer-Typen anwendbar und sehr gebräuchlich in Zählschleifen (int) und Arbeiten in Arrays (Pointer). Der Operand muß natürlich ein Lvalue sein.
    Werden Ausdrücke mit diesen Operatoren als Teilausdrücke in größere eingebaut, kommt es auf die Stellung des Operators vor oder nach dem Operanden dagegen sehr wohl an.

    In ++b wird zuerst b um 1 erhöht. Der sich dann ergebende Wert ist der Wert des Ausdrucks ++b.
    a = ++b; entspricht also b += 1; a = b;

    In b++ wird zwar genauso b um 1 erhöht. Der Wert, der weiterverwendet wird, ist allerdings der, den b vorher hatte.
    Damit ist a = b++; äquivalent zu a = b; b += 1;

    Überlegt (oder probiert) mal selbst, welche Ausgabe der folgende Code erzeugt:

    #include <iostream>
    int main()
    {
        int i = 2; 
    
        std::cout << i   << " ";
        std::cout << i++ << " ";
        std::cout << i   << " ";
        std::cout << ++i << " ";
        std::cout << i   << "\n";
    }
    

    Unbedingt vermeiden solltet ihr, eine Variable, die in einem Ausdruck mehrfach vorkommt, auch mit mehreren Inkrement-Operatoren zu versehen. Das Ergebnis ist dann unvorhersagbar und undefiniert.

    #include <iostream>
    int main()
    {
        int i = 2; 
        int Ergebnis = ++i * i++;
        std::cout << "Ergebnis = ++i * i++ = " << Ergebnis << "\n";
        // wertet er zu erst den linken operatnen ++i aus dan ist Ergebnis 3 * 3 = 9
        // wertet er zu erst den rechten operanten i++ aus dan ist Ergebnis 4 * 2 = 8
        // aber ob er nun links oder rechts zu erst macht ist undefieniert
    }
    

    Wie kann man beide Operatoren für eine Klasse Überladen?

    class foo
    {
    public:
        foo(int nr):m_nr(nr) {}
    
        foo & operator++ ()    // Präfix
        {
            ++m_nr;
            return *this;
        }
    
        const foo operator++ (int)  // die Postfix-Version bekommt ein int-dummy Parameter
        {                           // um sie von der Präfix-Version unterscheiden zu können
            foo tmp(*this);
            ++(*this);       // benuzt den Präfix-Operator
            return tmp;
        }
    private:
        int m_nr;
    };
    

    Wieso 'const foo operator++ (int)' und nicht 'foo operator++ (int)' ?
    Es macht solche Sachen wie 'f++++' oder 'f++ = 10' Unmöglich. Was ist daran so schlimm?
    Ich zitiere mal HumeSikkins:

    Regel Nr.1 bei der Operatorüberladung:
    "Do as the ints do".

    int main()
    {
        int i = 0;
        i++++;
    }
    

    Das ist nicht erlaubt (temporäre build-ins können nie verändert werden).
    Also darf ein Foo f; f++++; auch nicht erlaubt sein!

    Regel Nr. 2: Mache deine Klassen intuitiv bedienbar.
    Was würde man intuitiv von einem f++++ erwarten?
    Wahrscheinlich, f += 2; Schließlich heißt f++ ja f += 1;

    Tatsächlich wird bei f++++ aber nicht f zweimal sondern nur einmal inkrementiert. Das zweite ++ wird auf das temporäre Objekt das von f++ geliefert wird angewendet. Dieses stirbt direkt nach dem Ausdruck. Das zweite Inkrement verpufft also in einer Wolke gelben Rauchs 🙂

    Kurz: Das const ist an dieser Stelle eine verdammt richtige Sache

    Was ist mit der Geschwindigkeit?
    Wie man nun sehen kann muss die Postfix-Version eine Kopie von sich anlegen, erst dann kann sie sich Inkrementieren.
    Am ende wird die Kopie mit den alten Wert zurück zu geben, dieses kann nicht per refernzen gesehen da die Kopie eine lokale Variable ist.
    Dieser Umstand macht Postfix-Version langsamer als die Präfix-Version.
    Die Ausnahme sind die Eingebauten Typen wie Integer und Pointer, da ist der Compiler in der Lage das zu Optimieren, trotzdem sollte man sich von anfangt an auf die Präfix-Version trimmen.

    Wieso macht der Compiler nicht eine Optimierung bei Klassen?

    for(std::list<int>::const_iterator i = daten.begin(); i != daten.end(); i++)
         cout << *i << "\n";
    
    // das kann er doch leicht erkennen und zu ++i machen
    
    for(std::list<int>::const_iterator i = daten.begin(); i != daten.end(); ++i)
         cout << *i << "\n";
    

    Wenn er das machen würde, dann würde ich ihn als kaputt bezeichnen, den man hat die Freiheit die Prä- und Postfix Operatoren ganz Verschiedene dinge machen zu lassen.
    Aber legt keine Überraschungseier die Operatoren sollen schon das machen was man von ihnen erwartet.

    Eine kleine Eselsbrücke um sich zu merken bei welcher Version der int-Dummy Parameter hingehört, i++ die Postfix-Version ist die Langsamer und hat deshalb den hässlichen Dummy Parameter bekommen.

    Die Prinzipien gelten auch für --i und i-- .

    PS.
    Man kann natürlich die Möglichkeit nicht ausschießen, das der Compiler besonders schlau ist und die Postfix-Version so gut optimiert das sie mindestens genau so schnell ist wie die Präfix-Version, aber das dürfte ehr die Ausnahme sein.

    [ Dieser Beitrag wurde am 07.09.2002 um 20:38 Uhr von Dimah editiert. ]



  • Ich finde diesen Beitrag gut und Sinnvoll, wurde Zeit, dass den mal jemand geschrieben hat.

    Ich find ++i eh schöner als i++ 😉



  • Unbedingt vermeiden solltet ihr, eine Variable, die in einem Ausdruck mehrfach vorkommt, auch mit mehreren Inkrement-Operatoren zu versehen. Das Ergebnis kann dann schnell unvorhersagbar werden

    Das Ergebnis ist nicht nur schnell unvorhersehbar sonder schlicht und einfach undefiniert. Und undefiniertes Verhalten will man nicht in seinem C++ Code haben 🙂

    Merksätze für die Postfix/Prefix
    Postfix: Increment and fetch
    Prefix: fetch and increment

    const foo operator++ (int)

    Vielleicht noch ne Begründung warum const foo -> Stichwort: f++++;



  • #include <iostream>
    int main()
    {
    int i = 2;

    std::cout << i << " " << i++ << " " << i << "\n";
    std::cout << i << " " << ++i << " " << i << "\n";
    }

    Das würde ich rausnehmen, da das Ergebnis implementation-defined ist.
    Die Reihenfolge der Parameterauswertung für Funktionen wird vom Standard nicht festgelegt.
    Der VC z.B. wertet von rechts-nach-links aus, was zur Ausgabe:
    3 2 2
    4 4 3

    führt.

    Das kann auf anderen Compilern aber anders sein.



  • ok ich baue das um zu

    #include <iostream>
    int main()
    {
        int i = 2; 
    
        std::cout << i   << " ";
        std::cout << i++ << " ";
        std::cout << i   << " ";
        std::cout << ++i << " ";
        std::cout << i   << "\n";
    }
    

    [ Dieser Beitrag wurde am 28.08.2002 um 18:54 Uhr von Dimah editiert. ]



  • Original erstellt von HumeSikkins:
    **Das Ergebnis ist nicht nur schnell unvorhersehbar sonder schlicht und einfach undefiniert. Und undefiniertes Verhalten will man nicht in seinem C++ Code haben 🙂
    **

    erlädigt

    Original erstellt von HumeSikkins:
    Vielleicht noch ne Begründung warum const foo -> Stichwort: f++++;

    hmm da muss ich noch überlegen was gegen f++++ sprach, also
    gegen
    f++ = /* irgen etwas */;
    weiss ich ja, was macht es für ein sinn den etwas zuzuweissen wenn es sowieso temporär ist



  • hmm da muss ich noch überlegen was gegen f++++ sprach

    Regel Nr.1 bei der Operatorüberladung:
    "Do as the ints do".

    int main()
    {
        int i = 0;
        i++++;
    }
    

    Das ist nicht erlaubt (temporäre build-ins können nie verändert werden).
    Also darf ein Foo f; f++++; auch nicht erlaubt sein!

    Regel Nr. 2: Mache deine Klassen intuitiv bedienbar.
    Was würde man intuitiv von einem f++++ erwarten? Wahrscheinlich, f += 2; Schließlich heißt f++ ja f += 1;

    Tatsächlich wird bei f++++ aber nicht f zweimal sondern nur einmal inkrementiert. Das zweite ++ wird auf das temporäre Objekt das von f++ geliefert wird angewendet. Dieses stirbt direkt nach dem Ausdruck. Das zweite Inkrement verpufft also in einer Wolke gelben Rauchs 🙂

    Kurz: Das const ist an dieser Stelle eine verdammt richtige Sache

    [ Dieser Beitrag wurde am 28.08.2002 um 14:00 Uhr von HumeSikkins editiert. ]



  • eigentlich ist doch auch

    int Ergebnis = ++i * i;
    

    undefiniert?

    [ Dieser Beitrag wurde am 28.08.2002 um 18:53 Uhr von Dimah editiert. ]



  • eigentlich ist doch auch

    int Ergebnis = ++i * i;

    das undefiniert?

    Ja.


Anmelden zum Antworten