Referenz auf ein temporäres Objekt?



  • Hier kommt es z.B. zur Katastrophe:

    #include <list>
    class X 
    {
        private:
            std::list<int> *liste;
        public:     
            X()
            {
                liste = new std::list<int>;
            }
            ~X()
            {
                delete liste;
            }
    
            void Methode()
            {
                liste->push_back(4711);
            }
    
    };
    
    X& Func(X& x)
    {
        return x;
    }
    
    int main()
    {
        X& x = Func( X() );    
        x.Methode();
        return 0;
    }
    


  • Bereitet keinerlei Probleme....



  • @thomas80d
    Alles klar. Danke, das war das Beispiel was mir nicht eingefallen ist images/smiles/icon_smile.gif

    Für den gcc muss man zum Glück noch ein paar const hinzufügen.
    Letztlich bestätigt das aber meine Annahme.

    Nochmal, danke images/smiles/icon_smile.gif



  • Original erstellt von Knuddlbaer:
    Bereitet keinerlei Probleme....

    Wie? Das läuft bei dir? Mit welchem Compiler?



  • Hab hier den Borland C++ Builder 5.

    Einstellung:

    ANSI
    Keine Optimierung (alles raus was ging)

    Lasst mich nu aber nich dumm sterben, weil das was ich oben drüber geschrieben hab stimmt dann net.

    Sei bitte so lieb und erklär mir mal was da passiert.
    (Eventuell per Mail wenns zu viel is images/smiles/icon_smile.gif Developer@rothmichael.de)

    Vielen Dank!

    Micha

    P.s.: beim BCB wird wie oben beschrieben das Objekt vor Eintritt in die Funktion erzeugt...

    [ Dieser Beitrag wurde am 10.01.2002 um 00:14 Uhr von Knuddlbaer editiert. ]



  • Hallo,
    die Frage ist, was ist der Scope des unbenannten temporären Objekts das durch X() erzeugt wird.
    Sollte dies im Scope der Funktion Func liegen, haben wir das typische "Referenz auf ein lokales Objekt"-Problem.
    Ist der Scope aber main, dann ist das Programm bis auf ein paar fehlende const korrekt.

    Hier noch mal eine richtigere Version

    #include <list>
    class X
    {
        private:
            std::list<int> *liste;
        public:
            X()
            {
                liste = new std::list<int>;
            }
            ~X()
            {
                delete liste;
            }
    
            void Methode() const
            {
                    cout << liste->size();
            }
    
    };
    
    const X& Func(const X& x)
    {
        return x;
    }
    
    int main()
    {
        const X& x = Func( X() );
        x.Methode();
        return 0;
    }
    

    gcc : Segmentation Fault.
    VC: Müll



  • Im BCB: Läuft.

    Hast Du ne Ahnung was der Standard dazu sagt ? Definiert undefiniert?



  • Hallo,
    leider finde ich im Standard keine 100% Aufklärung (vielleicht hat jemand anders ja mehr Glück als ich).
    So wie ich die Teile die ich gefunden habe interpretiere,
    ist der Code böse.
    Ein Parameter hat die Lebenszeit der Funktion. X() hat nur die Aufgabe einen Parameter zu initialisieren. Wäre der Parameter ein Value Parameter, dürfte das unbenannte Objekt danach sterben. Da der Parameter aber eine Referenz ist, muss das Objekt solange leben wie die Referenz selbst. Dies ist bis zum Ende der Funktion. Das Kopieren einer Referenz (am Ende der Funktion) hat keine Auswirkung auf die Lebenszeit des Referenten.

    Klar ist der Fall für const-Referenzen im Zusammenhang mit temporären Objekten:

    string CreateTemp()
    {
        return "fred";
    }
    const string& Func(const string& ref)
    {
         return ref;
    }
    
    int main()
    {
        // 1. Dies ist legal, da das temporäre Objekt laut Standard
        // solange existieren muss, wie die Referenz:
        const string& Str = CreateTemp();
        cout << Str << endl;
    
        // 2. Dies ist BÖSER, ILLEGALER Code. Das temporäre Objekt 
        // stirbt bereits innerhalb der Parameterliste. 
        const string& Str = Func( CreateTemp() );
        cout << Str << endl; // BÖSE!
    }
    

    Ich werde jetzt mal schlafen gehen, in der Hoffnung, dass morgen der heilige Stroustrup (oder irgendwer sonst) die definitive Lösung gepostet hat images/smiles/icon_smile.gif



  • Hallo,
    also manchmal sehe ich den Wald vor lauter Bäumen nicht. Und dann beim Zähneputzen wird alles glasklar images/smiles/icon_smile.gif

    Also:

    class X {...};
    X& Func(X& x)
    {
        return x;
    }
    
    int main()
    {
        X& x = Func( X() );
        // ...
        x.IrgendEineMethode();
    }
    

    Der Code ist illegal. Nicht auf Grund von Lebenszeiten, sondern schlicht und einfach weil eine nicht-konstante Referenz nicht an das unbenannte Objekt gebunden werden darf.

    Code wie dieser:

    const string& Func(const string& str)
    {
        cout << "in Func" << endl;
        return str;
    }
    
    int main()
    {
        const string& x = Func ( string("test") );
        cout <<  x << endl;
    }
    

    ist illegal, da die Lebenszeit des unbenannten Objekts nur von der Referenz abhängt, an die es gebunden wird. Eine Kopie der Referenz hat auf das Objekt keinen Einfluß. Zum Zeitpunkt des couts existiert also schon lange kein Objekt mehr.

    Fazit:
    1. Die Rückgabe von Referenzparametern als (konstante) Referenz ist sicher.

    2. Die Rückgabe von konstanten Referenzparametern als konstante Referenz ist nicht sicher, da ungültige Referenzen entstehen können.

    Gleich als Regel:
    Gebe niemals einen konstanten Referenzparameter als konstante Referenz zurück.

    Gute Nacht images/smiles/icon_smile.gif

    [ Dieser Beitrag wurde am 10.01.2002 um 03:16 Uhr von HumeSikkins editiert. ]



  • Original erstellt von HumeSikkins:
    **
    Gleich als Regel:
    **Gebe niemals einen konstanten Referenzparameter als konstante Referenz zurück
    **.
    **

    Das ist richtig und natürlich auch noch aus einem weiteren grund.

    Einem compiler ist es erlaubt bei const referenzen temporäre objekte zu erzeugen. Verlangt eine funktion eine const referenz auf den typ A und es gibt eine konvertierungsmöglichkeit von B nach A, so kann ich der Funktion auch ein B übergeben, was er in ein (temporäres) A umwandelt. Solch ein temporäres objekt darf natürlich nicht nach außen weitergeleitet werden...



  • Noch ein paar Ergänzungen von camper:

    camper schrieb:

    am code und der erklärung gibt es nichts auszusetzen, wohl aber an den schlussfolgerungen.

    1. Die Rückgabe von Referenzparametern als (konstante) Referenz ist sicher.

    std::string& foo(std::string& s) { return s; }
    int main()
    {
        std::string& s = foo( std::string() = "test" );
        std::cout << s;
    }
    

    natürlich kann man argumentieren, dass man so etwas nicht schreiben sollte. und da stimme ich zu. trotzdem bleibt festzuhalten, dass foo keinerlei kontrolle über die lebenszeit der objekte hat, die an ihren parameter gebunden werden. das ganze erinnert ein bisschen an das beispiel, mit dem Stroustrup in TC++PL temporäre objekte einführt:

    void f(string& s1, string& s2, string& s3)
    {
        const char* cs = (s1+s2).c_str();
        cout << cs;
        if (strlen(cs=(s2+s3).c_str())<8 && cs[0]=='a') {
            // cs used here
        }
    }
    

    hier geht es zwar nicht um die rückgabe von referenzen. der punkt ist aber der, dass die eigenschaft, temporär zu sein, einem objekt nicht an sich zuteil wird, sondern nur in einem kontext ensteht (der standard definiert auch nicht, was ein temporäres objekt ist, er sagt nur, wann sie entstehen). innerhalb von foo ist das objekt, an das s gebunden wird, nicht temporär. was foo garantieren kann (was irgendeine sauber geschriebene funktion garantieren kann) ist, dass ihr rückgabewert gültig bleibt bis zum ende des ausdrucks, in dem der funktionsaufruf stattfand. nicht mehr und nicht weniger. der typ des rückgabewertes ist dafür völlig unerheblich.

    2. Die Rückgabe von konstanten Referenzparametern als konstante Referenz ist nicht sicher, da ungültige Referenzen entstehen können.

    der rat ist richtig, aber die begründung nicht ausreichend. das mögliche entstehen einer ungültigen referenz ist direkte folge des aufrufenden code und dort erkennbar. es hat also an sich nichts mit der funktion selbst zu tun. wäre das problem der funktion anzulasten, so dürften z.b. memberfunktionen niemals referenzen auf *this zurückgeben.

    den fall gegen die rückgabe eines konstanten referenzparameters würde ich aus einem anderen grund machen. wann reichen wir denn üblicherweise parameter weiter? immer dann, wenn funktionsaufrufe (potentiell) verkettet werden sollen. z.b.

    std::cout >> a >> b;
    

    der operator >> reicht jeweils seinen linken parameter weiter. die wesentliche aufgabe des operators ist es, etwas aus den stream zu lesen, diesen also zu verändern. eine referenz auf const kommt somit gar nicht in betracht. ähnlich ist es bei memberfunktionen wie z.b. copy-assignment. diese funktion kann sinnvoll nur nicht-const sein. es stellt sich also die frage, welche sinnvolle aufgabe eine funktion, die einen konstanten referenzparameter bekommt und diesen zurückgibt haben kann. die funktion kann ja ihr ergebnis nicht direkt kommunizieren. damit bliebe noch die veränderung eines globalen zustands. der sinn eines funktionswertes ist ja eigentlich, ein ergebnis oder eine veränderung zu kommunizieren. gebe ich einen konstanten parameter direkt weiter, passiert genau das nicht.

    camper schrieb:

    das beispiel mit der zuweisung war noch sehr deutlich. ein weitaus weniger offensichtlich esoterisches beispiel entstünde, falls wir das "name-constructor" idiom einsetzen (wenn ich mich nicht irre, wird das in dieser faq auch mal erwähnt). a la

    struct X
    {
        X() {}
        int a_, b_, c_;
        X& set_a(int a) { a_ = a; return *this; }
        X& set_b(int b) { b_ = b; return *this; }
        X& set_c(int c) { c_ = c; return *this; }
    };
    X& foo(X&);
    int main()
    {
       X& x = foo( X().set_a( 0 ).set_b( 1 ).set_c( 2 ) );
    // benutze x - ups
    ]
    

    hier bauen wir unser X ganz so wie es gedacht ist. und trotzdem ist das ergebnis eine katastrophe 🙂


Anmelden zum Antworten