warning: returning reference to temporary



  • Moinsen Community,

    ich benutze G++ 4.8 und mich macht eine Warnung etwas stutzig.

    // get name
        const std::string& get_name() const{
            return names.empty() == true ? std::string() : names[0]; // <== Warnung
        }
    

    Aber warum spuckt der überhaupt eine Warnung da raus, wenn ich doch eine konstante Referenz zurückgebe? Einfach -Wreturn-local-addr abschalten und mir keinen Kopf darüber machen, oder sollte ich doch lieber nur std::string zurückgeben?

    Grüße.



  • Ähm, temporäre Objekte werden nach der Abarbeitung einer Funktion nun mal zerstört. Eine Referenz auf ein zerstörtes Objekt ist nun mal nicht so sinnvoll. 😉



  • Liegt daran dass du eine Referenz auf ein temporäres string-Objekt zurückgibst, das nach dem return Statement zerstört wird.

    Mach es so:

    const std::string& get_name() const
    {
        static std::string empty;
        return names.empty() == true ? empy : names[0];
    }
    


  • Ach jetzt sehe ich was du meinst, const-ref... const-ref sollte eigentlich ein temp. Objekt am Leben erhalten, hmm gute Frage hätte jetzt auch gedacht, dass das so geht?



  • out schrieb:

    hätte jetzt auch gedacht, dass das so geht?

    nö.

    du hast da an so einen kontext gedacht:

    :::
    
    int main() {
      string const& ref = string();
      // ref ist hier noch gültig
    }
    

    Aber in deinem Fall trifft die "Lebenszeitverlängerungsregel" nicht zu. Du kannst da keine große Magie erwarten. In dem von mir gezeigten Fall ist das mit dem Verlängern aus Sicht des Compilers eine sehr einfach Sache.


  • Mod

    Ethon schrieb:

    Mach es so:

    const std::string& get_name() const
    {
        static std::string empty;
        return names.empty() == true ? empy : names[0];
    }
    

    Nein. Wenn dann so:

    const std::string& get_name() const
    {
        if (names.empty) throw out_of_range("Du hast doofe Ohren.");
        return names[0];
    }
    

    Oder auch ganz ohne Prüfung. Wer versucht auf ein potentiell leeres Objekt zuzugreifen, der soll damit genau so auf die Schnauze fallen, wie jemand, der front() auf einem leeren vector aufruft. Ganz schlecht an dem Vorschlag, einen leeren String zurück zu geben, ist, dass es auch semantisch falsch ist. Es ist eine Lüge. Der Name ist nicht leer. Es gibt überhaupt keinen Namen! In welchem Szenario soll solch eine Lüge sinnvoll sein?


  • Mod

    out schrieb:

    const-ref sollte eigentlich ein temp. Objekt am Leben erhalten

    unter bestimmten Umständen.

    SeppJ schrieb:

    Nein. Wenn dann so:

    const std::string& get_name() const
    {
        if (names.empty) throw out_of_range("Du hast doofe Ohren.");
        return names[0];
    }
    

    Kann man auch so schreiben

    const std::string& get_name() const
    {
        return !names.empty ? names[0] : throw out_of_range("Du hast doofe Ohren.");
    }
    

    Finde ich übersichtlicher, das ist aber vermutlich eine Minderheitsposition.



  • Hab mal kurz nachgedacht. Eine Funktion gibt doch theoretisch immer ein temp. Objekt zurück (darum ja auch RVO)... In dem Fall ist das ja praktisch die Referenz... Und die geht nach Abarbeitung der Funktion flöten, damit geht auch das andere tmp. Objekt flöten, auf das die Referenz zeigt...


  • Mod

    out schrieb:

    Hab mal kurz nachgedacht. Eine Funktion gibt doch theoretisch immer ein temp. Objekt zurück (darum ja auch RVO)

    Nein. Das ist einfach falsch und hat nichts mit RVO zu tun. Eine Funktion kopiert ihren Rückgabetyp. Da kommt eventuelle RVO ins Spiel, dass die Kopie wegoptimiert wird. Aber da ist an keiner Stelle etwas temporär.

    Hier kannst du dir vorstellen, dass der Referenzwert kopiert wird (wenn man sich Referenzen mal als verkappte Pointer denkt). Aber nicht das Objekt, auf das sich die Referenz bezieht. Das ist wie bei der Rückgabe von Pointern. Der Pointer wird kopiert, aber nicht das, auf was er zeigt.

    edit: Camper hat's unter mir viel besser erklärt 👍


  • Mod

    out schrieb:

    Hab mal kurz nachgedacht. Eine Funktion gibt doch theoretisch immer ein temp. Objekt zurück (darum ja auch RVO)... In dem Fall ist das ja praktisch die Referenz... Und die geht nach Abarbeitung der Funktion flöten, damit geht auch das andere tmp. Objekt flöten, auf das die Referenz zeigt...

    Im Normalfall, wenn keine Referenz zurückgeben wird und es sich nicht um einen skalaren Typen handelt, ist der Rückgabewert ein temporäres Objekt, das zerstört wird am Ende der Auswertung des Ausdruckes, der den Funktionsaufruf enthält. Eine Referenz wiederum ist Nichts, was überhaupt irgendwie "leben" würde und also zerstört werden könnte. Die Lebenszeitverlängerung, wenn ein temporäres Objekt an eine Referenz gebunden wird, beruht nicht darauf, dass die Referenz lebt, sondern dass sie einen Namen hat. Namen haben Scopes und diese Scopes kann man heranziehen, um sinnvoll über veränderte Lebenszeiten zu diskutieren. Der Rückgabewert einer Funktion dagegen ist immer etwas Namenloses.
    Nochmal zur Erinnerung die Ausnahmefälle ohne Lebenszeitverlängerung:
    1. Binden eines temporären Objektes an einen Funktionsparameter - hier lebt das temporäre Objekte sowieso schon länger.
    2. bei Referenzrückgabe einer Funktion (s.o.)
    3. Binden eines temporären Objektes an ein Referenzmember in der Konstruktotinitialisierungsliste (sowieso ziemlich exotisch, Objekt lebt in diesem Fall bis zum Ende des Konstruktors, aus technischen Gründen - sonst müsste das Objekt ja während seiner gesamten Lebenszeit Bch führen, welche Member durch Temporaries erzeugt wurden).
    4. (C++11) Temporäries in new-Initialisierern (eine Variante von 3., die die neue Initialisierungssyntax abdeckt).



  • SeppJ schrieb:

    Ethon schrieb:

    Mach es so:

    const std::string& get_name() const
    {
        static std::string empty;
        return names.empty() == true ? empy : names[0];
    }
    

    Nein. Wenn dann so:

    const std::string& get_name() const
    {
        if (names.empty) throw out_of_range("Du hast doofe Ohren.");
        return names[0];
    }
    

    Oder auch ganz ohne Prüfung. Wer versucht auf ein potentiell leeres Objekt zuzugreifen, der soll damit genau so auf die Schnauze fallen, wie jemand, der front() auf einem leeren vector aufruft. Ganz schlecht an dem Vorschlag, einen leeren String zurück zu geben, ist, dass es auch semantisch falsch ist. Es ist eine Lüge. Der Name ist nicht leer. Es gibt überhaupt keinen Namen! In welchem Szenario soll solch eine Lüge sinnvoll sein?

    Warum sollte es nicht sinnvoll sein wenn ein Objekt keinen Namen hat? Das macht je nach Kontext sehr viel Sinn!
    Deswegen hier die beste Lösung:

    boost::optional<const std::string&> get_name() const
    {
        return names.empty() ? boost::none : names[0];
    }
    

  • Mod

    Ethon schrieb:

    Warum sollte es nicht sinnvoll sein wenn ein Objekt keinen Namen hat?

    Es kann ja offensichtlich auch keinen Namen haben. Das ist nicht der Sinn meines Beitrags. Die Aussage ist: Wenn man auf den nicht vorhandenen Namen zugreift, dann sollte die Zugriffsmethode doch bitte keinen Namen erfinden! Wenn ein nicht vorhandener Name einem leeren String entsprechen soll, dann hat dies bei der Namensgebung so gesetzt zu werden.

    Dein vorheriger Vorschlag entspräche auf eine gewisse bekannte Containerklasse übertragen:

    template<typename T> class vector
    {
     // ...
     T &operator[](size_t index) { return  (index >= size) ? T() : data[index]; }
    };
    

    Das kommt dir hoffentlich nicht sehr sinnvoll vor. Wenn etwas nicht da ist, dann denkt man sich nicht einfach etwas aus. Das ist ein Logikfehler im Programm, wenn auf etwas nicht-vorhandenes zugegriffen wird, und sollte auch so behandelt werden. Let it crash!



  • ampersand schrieb:

    ...

    // get name
        const std::string& get_name() const{
            return names.empty() == true ? std::string() : names[0]; // <== Warnung
        }
    

    Auch wenn das etwas an der Frage vorbeiführt: Der Name einer Funktion sollte immer so präzise wie möglich sagen, was sie tut. name[] ist ja vermutlich ein vector aus beliebig vielen Namen, von denen nur Element 0 zurückgegeben wird. Daher wäre der bessere Name: get_first_name() .

    Daraus folgt, dass der aufrufende Code wissen und unterscheiden können sollte, ob ein solcher "erster Name" überhaupt existiert (empty) oder ob er existiert, aber leer ist. Diesen Unterschied muss die Funktion bereitstellen, denn sie kann nicht wissen (-> sollte nicht wissen müssen!), ob es für den aufrufenden Code egal ist oder nicht. Denke immer so, als ob nur Deine Funktion als Ware verkauft wird und irgendwo in komplett anderer Umgebung laufen soll. Das heißt:
    Der aufrufende Code sollte selbst den Leer-Check durchführen und ggf. für Ersatz sorgen, wenn das aus der Aufgabenstellung heraus sinnvoll ist. Wenn er trotzdem versucht, sich den "ersten" Namen zurückgeben zu lassen, obwohl keiner da ist, schlägt - als Selbst-Versicherung Deiner Funktion - die Exception zu.



  • Danke für die Antworten!
    Dann schmeiß ich halt ne Exception in die Runde, wenn der Vektor leer ist.

    Grüße.



  • Dazu fällt mir was ein:

    Stell Dir vor, Du hast einen Spezialvertrag mit der Post, dass sie Dir jedes Mal, wenn Du hingehst, den am weitesten vorne liegenden Brief aus Deinem Postfach geben.

    Eines Tages fangen sie an, Dir leere Briefumschläge in die Hand zu drücken, weil im Postfach eigentlich keine Briefe mehr drin sind, uns sie in ihrer Not nicht wissen, was tun, denn sie müssen ja Briefe liefern... (OK, sie müssen sie nur zeigen...) Und es hört nicht auf...

    Da sollte mal einer sagen: "He, Dein Postfach ist leer."

    -------------
    edit:
    Wenn Du statt names[0] die Methode at() verwendest ( names.at(0) ), kriegst Du die Exception out_of_range frei Haus geliefert, wenn der Vector leer ist, Du musst den Test dann selbst nicht machen.
    siehe: http://www.cplusplus.com/reference/vector/vector/at/

    Fairerweise sollte man noch erwähnen, dass ein anderer STL-container, map , beim Aufruf von myMap[key] selbständig ein neues Element anlegt und zurückgibt, wenn key nicht existiert. In C++11 gibt es immerhin auch die Methode map::at(key) , die in diesem Fall die Exception wirft. Hier kann man sich je nach Semantik die passende Funktion raussuchen.


Anmelden zum Antworten