Referenzen in C++



  • Hey,

    ich muss heute doch mal nachfragen, wie es sich mit den Referenzen genau verhält. Der Eintrag in der FAQ hat mehr Fragen aufgeworfen, als benatwortet. Ich versuch mal alles der Reihe nach aufzulisten:

    Beispiel 1:

    foo& func()
    {
        return foo();
    }
    
    foo& ref = func();
    

    Illegal, weil unbenannte Referenzen nicht an nicht konstante Referenzen gebunden werden dürfen.
    Frage: ist foo() überhaupt ein gültiger Konstrukturaufruf?

    Beispiel 2:

    foo& func()
    {
        foo tmp;
        return tmp;
    }
    
    foo& ref = func();
    

    Illegal, weil das foo-Objekt zum Ende der Funktion zerstört wird. Referenzen beeinflussen nicht die Lebensdauer von Objekten.

    Beispiel 3:

    const foo& func()
    {
        return foo();
    }
    
    const foo& ref = func();
    

    Illegal. Das unbenannte, temporäre Objekt darf zwar an eine Konstante Referenz gebunden werden, das referenzierte Objekt wird aber trotzdem zum Ende der Funktion zerstört.

    Beispiel 4:

    foo& func(foo& f)
    {
        return f;
    }
    
    foo& ref = func(foo());
    

    Illegal, Versuch ein temporäres Objekt an eine nicht-konstante Referenz zu binden.

    Beispiel 5:

    const foo& func(const foo& f)
    {
        return f;
    }
    
    const foo& ref = func(foo());
    

    Das temporäre foo-Objekt liegt im Gültigkeitsbereich von func. Deshalb wird es nach der Funktion auch wieder zerstört. Kann zwar aufgerufen werden, ref ist aber an kein gültiges Objekt gebunden.

    Gut, das wäre es vorerst. Ich hoffe, zumindest einige male richtig zu liegen. 🙂
    Ansonsten korrigiert mich bitte mit einer kleinen Erklärugn. 🙂


  • Mod

    Referent schrieb:

    Beispiel 1:

    foo& func()
    {
        return foo();
    }
    
    foo& ref = func();
    

    Illegal, weil unbenannte Referenzen nicht an nicht konstante Referenzen gebunden werden dürfen.
    Frage: ist foo() überhaupt ein gültiger Konstrukturaufruf?

    Nicht legal (ill-formed, unter der Annahme, dass die Top-Level cv-Qualifikation nicht foo const ist) ist hier

    return foo();
    

    foo() erstellt ein value-initialisiertes rvalue vom Typ foo - falls foo eine Klasse ist, resultiert das im Aufruf des Defaultkonstruktors. Illegal ist das statement deshalb, weil Referenzen auf nicht konstantes T nicht an rvalues gebunden werden können.
    (Ich will das hier nicht vertiefen, denn dafür wäre ein eigener faq-Eintrag angebracht: Ausdrücke sind entweder lvalues oder rvalues. Einfach gesagt repräsentieren skalare lvalues Objekte, während skalare rvalues Werte repräsentieren. Haben wir es mit Klassen zu tun, ist die Einteilung in diesem Sinne nicht mehr sinnvoll, allerdings bleibt die Klassifizierung entsprechend der syntaktischen Form bestehen.)

    Referent schrieb:

    Beispiel 2:

    foo& func()
    {
        foo tmp;
        return tmp;
    }
    
    foo& ref = func();
    

    Illegal, weil das foo-Objekt zum Ende der Funktion zerstört wird. Referenzen beeinflussen nicht die Lebensdauer von Objekten.

    Undefiniert ist hier die Initialisierung der Referenz ref, an der Funktion selbst ist nichts auszusetzen (nur formal versteht sich).

    Referent schrieb:

    Beispiel 3:

    const foo& func()
    {
        return foo();
    }
    
    const foo& ref = func();
    

    Illegal. Das unbenannte, temporäre Objekt darf zwar an eine Konstante Referenz gebunden werden, das referenzierte Objekt wird aber trotzdem zum Ende der Funktion zerstört.

    Analog zu Beispiel 2.

    Referent schrieb:

    Beispiel 4:

    foo& func(foo& f)
    {
        return f;
    }
    
    foo& ref = func(foo());
    

    Illegal, Versuch ein temporäres Objekt an eine nicht-konstante Referenz zu binden.

    Analog zu Beispiel 1. Ill-formed ist hier der Funktionsaufruf

    func(foo());
    

    Referent schrieb:

    Beispiel 5:

    const foo& func(const foo& f)
    {
        return f;
    }
    
    const foo& ref = func(foo());
    

    Das temporäre foo-Objekt liegt im Gültigkeitsbereich von func. Deshalb wird es nach der Funktion auch wieder zerstört. Kann zwar aufgerufen werden, ref ist aber an kein gültiges Objekt gebunden.

    Hier ist tatsächlich alles in Ordnung. Aber wie richtig erkannt wurde, wird das Objekt, an das ref gebunden wurde, nach der Initialisierung der Referenz zerstört (und der Speicher freigegeben). Diese Referenz kann nicht weiter verwendet werden.



  • Danke für die Antwort! 🙂 Wenn ich das richtig verstehe, dann liege ich mit meinen Annahmen immer richtig? (Wenn man davon absieht, dass ich das mangels Fachkenntnis alles deutlich vereinfacht dargestellt habe). 😉

    Und was sagt ill-formed genau aus?

    Was mir jetzt zu foo() noch auffällt: Das könnte ja genau so gut eine Funktion sein. Mein Compiler hängt sich auch auf, wenn es neben der Klasse foo noch eine Funktion foo gibt. Gibt es eine Möglichkeit, das eindeutig auszudrücken? Für den default-ctor bei benannten Objekten lässt man dazu ja einfach die Klammern weg.


  • Mod

    Referent schrieb:

    Danke für die Antwort! 🙂 Wenn ich das richtig verstehe, dann liege ich mit meinen Annahmen immer richtig? (Wenn man davon absieht, dass ich das mangels Fachkenntnis alles deutlich vereinfacht dargestellt habe). 😉
    Und was sagt ill-formed genau aus?

    Ein Fehler, der vom Compiler als solcher erkannt und angezeigt werden muss (einschließlich Syntaxfehlern) oder ein Verstoß gegen die ODR. Im Gegensatz zu Verstößen, die der Compiler erkennen darf aber nicht muss und solchen Konstrukten, die bloß (möglicherweise nur bei bestimmtem Input) undefiniertes Verhalten haben.

    Referent schrieb:

    Was mir jetzt zu foo() noch auffällt: Das könnte ja genau so gut eine Funktion sein. Mein Compiler hängt sich auch auf, wenn es neben der Klasse foo noch eine Funktion foo gibt. Gibt es eine Möglichkeit, das eindeutig auszudrücken? Für den default-ctor bei benannten Objekten lässt man dazu ja einfach die Klammern weg.

    Im gleichen Namensraum können entweder eine Klasse oder eine Aufzählung und entweder ein Objekt, eine (möglicherweise überladene) Funktion oder eine Aufzählungskonstante gleichen Namens existieren. In diesem Falle wird die Klasse bzw. der Aufzählungstyp verdeckt. Dieses Feature existiert nur zwecks C-Kompatibilität, denn dort haben Tagnamen ihren eigenen Namensraum. Ansonsten sollte man diese Möglichkeit nicht nutzen. In einer solchen Situation kann man wie in C auf die Klasse bzw. die Aufzählung zugreifen, indem man enum,struct,class,union davorschreibt. Unabhängig davon wären obige Beispiele, wenn foo eine Funktion wäre, alle ill-formed. Dann ist schon die Referenzdeklaration fehlerhaft, da foo kein Typ wäre. Sollte foo dagegen ein Funktionstyp sein, wäre der Ausdruck foo() ill-formed, Beispiel 2 allerdings möglich - natürlich müsste dann tmp noch irgendwo definiert werden, denn

    foo tmp;
    

    ist dann nur eine Funktionsdeklaration.


Anmelden zum Antworten