Wann Out-Parameter als Rückgabewert?



  • Hallo,

    2015 und ich weiß immer noch nicht, wie eine C++11-Funktion mittlerweile aussehen sollte 😞

    Wie sieht bei euch eine Funktion aus, die z.B. einen vector<int> zurück gibt?

    Bei mir weiterhin

    void f(vector<int>& v);
    

    Nachteilig ist da ja soweit ich sehe nur der unschöne Aufruf.
    Bei

    vector<int> f();
    

    ist der Move-Ctor ja nicht völlig umsonst.
    Vorteil ist der 'schönere/klarere' Aufruf.
    Was ist denn da jetzt recommended? Oder macht man das abhängig davon, wie gut der Rückgabewert 'movable' ist?



  • IMHO sollte eine Funktion Werte per return liefern und Fehler mit Exceptions melden. Weicht sie davon ab, sollte dies gut begründbar sein.



  • Wenn du f(vec) verwendest, dann bedeutet das dass du einen vector hast der bereits befüllt ist und du inplace darauf arbeitest. (zB sort)

    Wenn du vec=f() verwendest, dann bedeutet das dass du keinen vector hast und f dir einen erstellt. (zB split)



  • Jockelx schrieb:

    Hallo,
    Nachteilig ist da ja soweit ich sehe nur der unschöne Aufruf.

    Du kannst den Rückgabewert nicht als Ausdruck verwenden, um damit z.B. Konstanten zu initialisieren:

    const std::vector< int > hausnummern = f();
    

    Du must dem Kind einen Namen geben, obwohl Du den Namen gar nicht weiter verwende willst:

    std::vector< int > temp; // mir fällt kein besserer Name ein :-(
    f(temp);
    
    calc( temp );
    

    vs.

    calc( f() );
    

    Solange mir kein profiler sagt, dass ich es anders machen sollte, würde ich immer erst einmal auf die Lesbarkeit achten. Und meiner Meinung nach ist die

    vector<int> f();
    

    -Variante die besser lesbare.

    mfg Torsten



  • Ja, das macht alles Sinn.
    Das reicht mir auch schon.

    Dankeschön!



  • Es gibt auch noch diese Variante, wenn es nicht wirklich um vector geht, sondern um eine Folge:

    template <class OutputIterator>
    void f(OutputIterator results);
    


  • TyRoXx schrieb:

    Es gibt auch noch diese Variante, wenn es nicht wirklich um vector geht, sondern um eine Folge:

    template <class OutputIterator>
    void f(OutputIterator results);
    

    Das ist ja so kaputt und es sagt keiner was? 😮

    Wenn irgendwo "Iterator" steht, dann erwartet man da auch einen Iterator drin vor zu finden.
    Was aber bei der Signatur überhaupt keinen Sinn ergibt. Zum einen kann der Iterator kein out parameter sein, da er ja per value übergeben wird. Also müsste man da einen Iterator rein geben,
    auf den geschrieben werden kann.

    std::vector<int> data(42);
    f(data.begin());
    

    Da fällt aber sofort auf, dass Iteratoren immer paarweise auftreten. Der Caller müsste also wissen,
    wieviele results es den geben kann und entsprechend viel Speicher bereitstellen. Sonst passiert sowas:

    void f(OutputIterator results){
      for(size_t i=0; i<100; i++, results++) *results = i;
    }
    
    std::vector<int> data(42);
    f(data.begin());
    

    Angenommen, man ändert die Signatur auf eine OutputIterator& , dann könnte man zwar auf eine
    in f() lokale oder gar globale Variable einen Iterator zurückgeben. Dann hat man weiterhin das Problem, dass man keinen zweiten Iterator hat und man so über das Ende hinaus lesen oder schreiben könnte. Dazu kommt dann noch, dass der Caller den richtigen Datentyp kennen muss.

    Für praktisch alle Fälle ist ein return-by-value zu bevorzugen. Für alle STL Container hat man im schlimmsten Fall ein move. Viel wahrscheinlicher ist allerdings, dass der Compiler eine copy elision macht. Dann kostet eine Übergabe per "output"-Referenz sogar mehr! Davon abgesehen ist beim befüllen von Containern im allgemeinen nicht die Übergabe hinterher der Flaschenhals, sondern das Allokieren des Speichers und das Füllen des Containers selbst.



  • oh, diese schmerzen schrieb:

    TyRoXx schrieb:

    Es gibt auch noch diese Variante, wenn es nicht wirklich um vector geht, sondern um eine Folge:

    template <class OutputIterator>
    void f(OutputIterator results);
    

    Das ist ja so kaputt und es sagt keiner was? 😮

    Das ist überhaupt nicht kaputt. Man verwendet das Konzept einfach nur nicht so, wie du dir das vorstellst.
    Man möchte an der Stelle so etwas wie einen std::back_insert_iterator übergeben.

    std::vector<int> results;
    f(std::back_inserter(results));
    

    Das funktioniert auch mit anderen Containern, ostreambuf_iterator und so weiter.
    Außerdem behält man die Kontrolle über Speicheranforderungen. Damit muss f nichts zu tun haben.



  • Shade Of Mine schrieb:

    Wenn du f(vec) verwendest, dann bedeutet das dass du einen vector hast der bereits befüllt ist und du inplace darauf arbeitest. (zB sort)

    Wenn du vec=f() verwendest, dann bedeutet das dass du keinen vector hast und f dir einen erstellt. (zB split)

    Manchmal kann auch eine wirklich gute Erklärung so einfach sein 👍



  • oh, diese schmerzen schrieb:

    TyRoXx schrieb:

    Es gibt auch noch diese Variante, wenn es nicht wirklich um vector geht, sondern um eine Folge:

    template <class OutputIterator>
    void f(OutputIterator results);
    

    Das ist ja so kaputt und es sagt keiner was? 😮

    Wie TyRoXx schon selbst geschrieben hat ist das gar nicht kaputt. Es ist ganz im Gegenteil sogar ein ziemlich weit verbreitetes C++ Idiom.

    Man kann natürlich Output-Iteratoren ala std::back_inserter() generell in Frage stellen. z.B. weil sie sich kaum performant für extremst simple Dinge wie push_back an einen std::vector<char> oder das Schreiben von Bytes in ein File implementieren lassen.
    Ändert aber nix daran dass es wie gesagt ein weit verbreitetes C++ Idiom ist 😉


Anmelden zum Antworten