Return struct oder pointer zum return struct?



  • Man könnte aber auch einfach einen Pointer übergeben, wenn man meint dies tun zu müssen und fertig. Wieso auf einmal new ins Spiel kommt würd mich auch interessieren. Weder der Programmierer noch der Compiler brauchen da irgendwo dynamischen Speicher. Außerdem ging ich irgendwie davon aus, dass wir es hier eher mit C als mit C++ zu tun haben...



  • dot schrieb:

    Man könnte aber auch einfach einen Pointer übergeben, wenn man meint dies tun zu müssen und fertig. Wieso auf einmal new ins Spiel kommt würd mich auch interessieren. Weder der Programmierer noch der Compiler brauchen da irgendwo dynamischen Speicher. Außerdem ging ich irgendwie davon aus, dass wir es hier eher mit C als mit C++ zu tun haben...

    new kommt hier ins Spiel, wenn man eine neue Variable anlegen muss und sie per Pointer zurückgeben muss, denn wie schon erörtert ist alloca() überhaupt nicht standardkonform.



  • bla my_return_struct;
    foo(&bla);
    

    😕



  • dot schrieb:

    bla my_return_struct;
    foo(&bla);
    

    😕

    So, und genau das wird von allen gängigen Compilern genau so optimiert. Eventuell sogar noch ohne Defaultkonstruktoraufruf.

    Was ich meine, was vom Programmierer besser optimiert werden könnte als vom Compiler ist etwa folgendes:

    struct abc
    {
        bool what;
        int very_big_array[10000];
    };
    
    abc *foo1(abc *a1, abc *a2)
    {
        ret = a1->what ? a1 : a2;
    }
    
    abc foo2(abc const &a1, abc const &a2)
    {
        return a1.what ? a1 : a2;
    }
    
    int main1()
    {
        abc a1, a2;
        //Initialisierung
        abc *ret = foo1(&a1, &a2); //keine Kopie
        //Array von ret ausgeben
    }
    
    int main2()
    {
        abc a1, a2;
        //Initialisierung
        abc ret = foo2(a1, a2);  //eine Kopie üblicherweise
        //Array von ret ausgeben
    }
    

    Ein anderer Fall wäre, dass die beiden foo()-Varianten if-verzweigt sind und zwei Variablen b1 und b2 enthält. Im einen Zweig wird b1 zurückgegeben, im anderen Zweig b2. Nun musst du 2 Variablen erstellen, der Compiler kann die Rückgabekopie auch per NRVO nicht optimieren. Mit deiner Variante passiert exakt das Gleiche wie beim Compiler ohne Optimierung: Konstruktion von b1 und b2, Kopie in den Zielspeicher. Wird dagegen die Variable innerhalb von foo1() per new alloziert und einfach der Pointer zurückgegeben, entfällt die Rückgabekopie. Dafür hast du dann halt den Allokationsaufwand.



  • wxSkip schrieb:

    So, und genau das wird von allen gängigen Compilern genau so optimiert.

    Wie du selbst sagst aber eben nicht immer. Wenn es sich nicht gerade um triviale Fälle handelt, kann sich also kaum drauf verlassen. Nicht mehr und nicht weniger hab ich je gesagt. Wenn es also performancekritisch ist, dann kann man es auch einfach selbst machen. So Wahnsinnig kompliziert isses ja jetzt nicht gerade. Was den Defaultkonstruktor angeht: Ich ging wie gesagt davon aus, dass es dem Threadersteller um C geht, was natürlich POD impliziert. In C++ wird man sowas generell wohl eher selten machen.



  • dot schrieb:

    wxSkip schrieb:

    So, und genau das wird von allen gängigen Compilern genau so optimiert.

    Wie du selbst sagst aber eben nicht immer. Wenn es sich nicht gerade um triviale Fälle handelt, kann sich also kaum drauf verlassen. Nicht mehr und nicht weniger hab ich je gesagt. Wenn es also performancekritisch ist, dann kann man es auch einfach selbst machen. So Wahnsinnig kompliziert isses ja jetzt nicht gerade. Was den Defaultkonstruktor angeht: Ich ging wie gesagt davon aus, dass es dem Threadersteller um C geht, was natürlich POD impliziert. In C++ wird man sowas generell wohl eher selten machen.

    Doch. Das Ergebnis wird (praktisch) immer vor dem Zurückgeben in das Zielobjekt geschrieben. Die Frage ist bloß, ob gleich eine Variable, die immer zurückgegeben wird, direkt mit dem Zielspeicher identisch ist, oder ob die Rückgabevariable eben nicht direkt im Zielspeicher erstellt werden kann und erst dort hinein kopiert werden muss. Daran ändert auch dein Code nichts. Bei meinem Code gibt es hingegen gar keine Zielvariable, sondern nur einen Pointer darauf.


  • Mod

    Praktisch jede vernünftig geschriebene Funktion lässt sich in eine Form bringen, mit der NRVO trivial anzuwenden ist. RVO ist sowieso trivial.

    dot schrieb:

    So Wahnsinnig kompliziert isses ja jetzt nicht gerade.



  • @wxSkip: Dir ist schon klar, daß die erste Variante gar nichts macht? foo1() ändert den (lokalen) Parameter und hat keine Auswirkungen auf das ausrufende Programm.



  • camper schrieb:

    Praktisch jede vernünftig geschriebene Funktion lässt sich in eine Form bringen, mit der NRVO trivial anzuwenden ist. RVO ist sowieso trivial.

    dot schrieb:

    So Wahnsinnig kompliziert isses ja jetzt nicht gerade.

    Was wenn du mehrere Returnpfade hast? Händisch isses auch dann noch trivial, aber MSVC z.B. würde dort afaik zumindest keine NRVO anwenden. Ich sag ja auch gar nix gegen RVO. Wenn man weiß was man tut und sich auf seine(n) Compiler verlassen kann, dann wird man das sinnvollerweise auch tun. Nur is das eben ein Pattern, auf das man in C sowieso ständig irgendwo trifft. Darum hab ich das einfach mal vorgeschlagen, anstatt der äußerst merkwürdigen Vorschläge, die davor so herumschwirrten ( alloca() in der Funktion z.B. 😮 ). Ich möchte nur verhindern, dass mancher jetzt vielleicht glaubt, dass man am besten einfach ohne nachdenken alles als Wert returned und davon ausgeht, dass der Compiler es schon richten wird.



  • CStoll schrieb:

    @wxSkip: Dir ist schon klar, daß die erste Variante gar nichts macht? foo1() ändert den (lokalen) Parameter und hat keine Auswirkungen auf das ausrufende Programm.

    Ups, wurde korrigiert.



  • wxSkip schrieb:

    dot schrieb:

    Soll der Aufrufer doch einen Pointer auf ein struct übergeben, das du ihm dann ausfüllst, so wie man das normalerweise macht eben...

    Und genau das passiert auch normalerweise, wenn man by-value zurückgibt. Der Compiler organisiert das so (schau mal nach RVO), dass du meistens direkt in das Resultat reinschreibst. Daher auch der Artikel. Oder habe ich hier etwas missverstanden?

    Hmja.
    OK. Wenn man ein wenig weiter denkt als die ursprüngliche Frage des OP, dann könnte es passen 🙂

    Dann könnte die Funktion nämlich u.U. so aussehen:

    ppackage myfunc(ppackage pack);
    

    Dann haben wir genau den Fall der in dem Artikel angesprochen wird.
    Macht allerdings nur Sinn wenn entweder

    1. bei den meisten Aufrufen von myfunc() auch wirklich was zurückgegeben wird oder
    2. das Paket mit dem myfunc() aufgerufen wird sehr oft ein Temporary ist bzw. "gemoved" werden kann

Anmelden zum Antworten