Referenz als Rückgabewert



  • stell dir vor ein:

    CString sinnlos("aber dumm");
    sinnlos = sinnlos;
    

    sinnlos löscht jetzt den speicher, allokiert stattdessen neuen
    und? es kopiert den neuen speicher in den neuen (der alte ist ja schon gelöscht)



  • I see. DAS meint ihr mit Selbstzuweisung. Alles klar. Also müsste ich noch sowas schreiben wie

    if(&rhs != this)
    

    oder

    if(rhs.c_str() != m_String)
    

    Gut, diese Frage ist beantwortet. Aber meine eigentliche Frage noch nicht. Wie kann ich es also hinbekommen, dass sich folgender Code kompilieren lässt UND (vor allem) dass er auch Sinn macht? Die Frage bezieht sih auf die ToUpper()-Funktion.

    CString cstr1("Hallo"); 
    CString cstr2; 
    
    cstr2 = cstr1.ToUpper();
    

    Und jetzt geht bitte davon aus, dass der Zuweisungsoperator ordentlich implementiert ist.



  • indem du ToUpper einen Wert zurückgeben läßt. Geht leider nicht besser, wenn du deinen Code nicht ändern willst, da der Compiler nicht intelligent genug ist bzw. nicht sein darf.

    man könnte aber z.b. erstens cstr2 gleich initialisieren, um return value optimization auszunutzen:

    CString cstr2 = cstr1.ToUpper();
    

    oder man könnte eine swap-Methode implementieren, die die Repräsentationen der Strings intern vertauscht, und dann so schreiben:

    CString cstr2;
    cstr2.swap(cstr1.ToUpper());
    

    Das erledigt gleich das im Grunde unnötige Kopieren im Zuweisungsoperator. Eigentlich ist der Compiler selbst intelligent genug, zu erkennen, dass du das temporäre Ergebnis von ToUpper nach der Zuweisung nicht mehr benötigst, und daher auch keine Kopie gemacht werden muss. Diese Optimierung verändert aber die Semantik des Programms, wenn dein Zuweisungsoperator Seiteneffekte hat, und darf daher nicht gemacht werden.

    Ich hoffe ich habe nicht völligen Blödsinn geschrieben 🙂



  • Dieso swap-Methode halte ich sowieso für sinnvoll.

    Dann wird der operator= nämlich mit

    CString temp(rhs);
    swap(*this, rhs);

    vollständig implementiert. Das Problem der Selbstzuweisung ist auch keines mehr und Exception.sicher ist es auch noch, sofern swap so implementiert ist, daß es nie wirft.

    Im anderen Fall ist nämlich die Frage was passiert, wenn die aktuelle Resource zwar mit delete [] freigegeben wird, beim erneuten anfordern aber bad_alloc fliegt... alte Daten weg, neue Daten gibts nich => inkonsistenter Zustand, Objekt kaputt.



  • Bashar schrieb:

    man könnte aber z.b. erstens cstr2 gleich initialisieren, um return value optimization auszunutzen:

    CString cstr2 = cstr1.ToUpper();
    

    Hm, heißt das nrvo funktioniert nur bei Initialisierungen?

    Also bei

    string k;
    k = func();

    kann der compiler nicht mehr optimieren?



  • Bashar schrieb:

    CString cstr2;
    cstr2.swap(cstr1.ToUpper());
    

    und jetzt erklär noch mal den unterschied zu

    CString cstr2;
    cstr2.operator=(cstr1.ToUpper());
    

    holla. steh ich auf der leitung?



  • DrGreenthumb schrieb:

    Hm, heißt das nrvo funktioniert nur bei Initialisierungen?

    Bei NRVO wird intern der Funktion ein Zeiger auf das Zielobjekt übergeben und das zurückgegebene Objekt direkt dort konstruiert. Bei Initialisierung klappt das wunderbar, aber bei Zuweisung nicht mehr: Der Zuweisungsoperator ist schließlich was anderes als der Kopierkonstruktor.
    Also wird erstmal ein temporäres Objekt erzeugt, auf welches dem Zuweisungsoperator eine Referenz übergeben wird. Da innendrin wird dann nochmal kopiert (wenn wir copy-on-write mal ausschließen)

    Da kommt jetzt davie's Einwand ins Spiel: Mit swap wird eben nicht kopiert. Die alte leere Hülle von cstr2 -- eh nur default-konstruiert -- wird beim Entsorgen des temporären Objekts weggeworfen. cstr2 übernimmt die interne Repräsentation des von ToUpper zurückgegebenen Objektes.

    Der Zuweisungsoperator kann nicht davon ausgehen, dass die rechte Seite nicht mehr gebraucht wird, muss also immer eine richtige Kopie machen (wenn wir COW wieder ausschließen)



  • menno.. sollte schlafen gehen. n8



  • Danke, dachte die linke Seite wird einfach als Parameter mitgegeben. Wusste nicht, das dass so kompliziert ist.. Aber wenn ich drüber nachdenke eigentlich logisch, das Objekt muss ja komplett neu konstruiert werden.



  • OK, dann hab ich jetzt noch ne Frage dazu:

    int Dec(int a)
    {
       int r = a + 1;
       return r;
    }
    
    int main()
    {
       int f = 1;
       int g;
       g = Dec(f);
       ...
    }
    

    Was passiert hier? Ist folgendes richtig?

    1. Es wird ein int f auf dem Stack reserviert und mit dem Wert 1 gefüllt
    2. Es wird ein int g auf dem Stack reserviert
    3. Es wird ein int r auf dem Stack reserviert und mit dem Wert 2 gefüllt
    4. Der Wert von r wird zu g [b]rüberkopiert[/b], so dass g mit 2 gefüllt wird
    5. Der Speicher, den r belegte, wird wieder freigegeben
    

    Zweite Frage: Was bedeutet const am Ende einer Funktionsdeklaration?

    CString CString::ToUpper() const;
    


  • zu Frage 2:
    Dieses const bedeutet IMHO, daß der Compiler den this-Zeiger als const this* an die Elementfunktion übergibt, d.h., die Funktion kann die Elemente der Klasse nicht verändern.

    Bitte korrigiert mich, wenn ich ich Unsinn erzähle, ist noch früh *gähn*



  • WebFritzi schrieb:

    3. Es wird ein int r auf dem Stack reserviert und mit dem Wert 2 gefüllt
    4. Der Wert von r wird zu g rüberkopiert, so dass g mit 2 gefüllt wird
    5. Der Speicher, den r belegte, wird wieder freigegeben

    Nein, eigentlich wird beim return zunächst ein temporäres Objekt erzeugt (als Kopie von r), dann wird r zerstört, und dann wird r (bzw. das temporäre Objekt) an g zugewiesen. Diese Erzeugung des temporären Objekts per Kopierkonstruktor kann mittels der schon angesprochenen Named Return Value Optimization (NRVO) vermieden werden. Was das ist, siehe Hume's FAQ: http://fara.cs.uni-potsdam.de/~kaufmann/?page=GenCppFaqs&faq=Optimize#Answ

    Edit: Aber du arbeitest hier mit int-Objekten, die werden auch ganz gerne mal in Registern (%eax) zurückgegeben, da ist diese NRVO-Geschichte nicht wirklich von Belang 😉

    Zweite Frage: Was bedeutet const am Ende einer Funktionsdeklaration?

    CString CString::ToUpper() const;
    

    Das bedeutet, dass ToUpper die Membervariablen seines CString-Objektes nicht verändern kann. Das erlaubt es, diese Methode auch auf const-Objekte aufzurufen. Siehe dein Lieblings-C++-Lehrbuch (vom Level her gehören die beiden Fragen eigentlich nicht annähernd in denselben Thread :þ )



  • Nein, eigentlich wird beim return zunächst ein temporäres Objekt erzeugt (als Kopie von r), dann wird r zerstört, und dann wird r (bzw. das temporäre Objekt) an g zugewiesen

    OK ich habe einen Stack. Da sind schon irgendwelche Werte drauf, die mich jetzt nicht interessieren. Dann werfe ich r drauf. Darauf lege ich ein temporäres Objekt. Jetzt habe ich ein Problem: Wie bekomme ich r vom Stack "gepopt", ohne vorher das temporäre objekt zu "popen".



  • Der Platz für den Rückgabewert wird schon vor dem Aufruf der Funktion freigemacht. Dh r liegt über dem temp-Objekt, und kann dann ganz normal runtergepoppt werden.



  • Ah, OK.


Anmelden zum Antworten