Objekt zurückgeben



  • Super, danke für die Erklärung. Wie würde denn ein getA mit Move-Semantik aussehen? Sorry für die Fragen, aber ich fange gerade an mit C++ und will aber keinen alten Standard mehr lernen, sondern das aktuelle C++11, was nun auch schon einige Jahre alt ist.



  • Du hast vermutlich an sowas gedacht:

    #include <iostream>
    #include <memory>
    #include <vector>
    
    struct A { int i = 0; };
    
    class B {
    private:
        std::vector<A> a = {{}, {}, {}}; // 3 Elemente
    
    public:
        auto ripMyHeartOut() {
            return std::move(a); // das hier ergibt nicht viel sinn
        }
    
        void kaboom()
        {
            std::cout << a.front().i;
        }
    };
    
    int main()
    {
        auto b = B{};
        auto&& rip = b.ripMyHeartOut();
        std::cout << rip.front().i;
        b.kaboom(); // <- autsch
    };
    

    Den obigen Code zu erklären bereitet mir in sofern Schwierigkeiten, als dass er keinen Sinn ergibt.
    Member aus einer Klasse rauszumoven erzeugt ein ähnliches Problem wie die Rückgabe der Referenz. a ist nun innerhalb von B nach dem Aufruf nicht mehr gültig.

    Man könnte sich nun fragen: was ist wenn ich das, was ich zurückgebe nicht mehr brauche? Dann kann ich es doch moven!

    int optimierungsHemmer() {
        int res;    
        // ...  tu etwas mit res
        return std::move(res);  
    }
    

    Dummerweise verhindert man damit eine Kompileroptimierung und hat sich selbst letztendlich nichts gutes getan. XValues muss man aus Funktionen nicht rausschieben, das macht der Compiler selbst schon besser.

    Mein Fazit: Ich kenne kein Grund move für returns zu verwenden.



  • Ah, verstehe die Problematik. Durch move bei Return würde man wieder Member ungültig machen.

    Aber das Moven von lokalen Objekten innerhalb der get-Methode macht schon Sinn, da man unnötigen Kopien vermeidet. Und das macht der Compiler wirklich von allein? Also er erkennt dass das lokale Objekt nicht mehr gebraucht wird und anstelle eine Kopie zurück zu geben macht er selbständig einen Move? Der Kopiekonstruktor wird also in diesem Falle gar nicht aufgerufen?



  • Objekter schrieb:

    Ah, verstehe die Problematik. Durch move bei Return würde man wieder Member ungültig machen.

    Aber das Moven von lokalen Objekten innerhalb der get-Methode macht schon Sinn, da man unnötigen Kopien vermeidet. Und das macht der Compiler wirklich von allein? Also er erkennt dass das lokale Objekt nicht mehr gebraucht wird und anstelle eine Kopie zurück zu geben macht er selbständig einen Move? Der Kopiekonstruktor wird also in diesem Falle gar nicht aufgerufen?

    Das ist die Named Return Value Optimization. Die gibts schon vor movüberhaupte und verhindert das überhaupt irgendein Kopier oder Movekonstrukto aufgerufen wird, weil das Objekt direkt im Ziel erzeugt wird.
    Verwendet man allerdings move, darf sie nicht mehr angewendet werden.



  • Ok dann vertraue ich hier also auf den Compiler, obwohl mir irgendwie wohler wäre, wenn man dieses Verhalten irgendwie im Code ausdrücken könnte.



  • Objekter schrieb:

    Ok dann vertraue ich hier also auf den Compiler, obwohl mir irgendwie wohler wäre, wenn man dieses Verhalten irgendwie im Code ausdrücken könnte.

    Dazu hat STL mal einen guten Vortrag gehalten - "Don't Help the Compiler." (https://www.youtube.com/watch?v=AKtHxKJRwp4). Es geht da nur um so "Optimierungen", die Programmierer machen, die eigentlich schlecht sind. Ist auf jeden Fall sehenswert, der Teil mit move ist ab 38:03.



  • Ja, danke, werde ich mir anschauen. Ist final nicht auch für die Optimierung da? Und wie sieht es mit const aus, ist das "nur" um die Variablen vor Modifikation zu schützen oder auch um dem Compiler Optimierungen zu erleichtern?



  • Objekter schrieb:

    Ja, danke, werde ich mir anschauen. Ist final nicht auch für die Optimierung da?

    Nein, final ist rein auf C++ Ebene und taucht (wie Klassen generell) nicht im Assemblercode auf*.

    Und wie sieht es mit const aus, ist das "nur" um die Variablen vor Modifikation zu schützen oder auch um dem Compiler Optimierungen zu erleichtern?

    const im Gegensatz bietet sehr wohl die Möglichkeit vieler Optimierungen.
    Ist der const Wert ein literaler Ausdruck (was bei constexpr verlangt wird) kann der Wert direkt in den Sourcecode (ähnlich wie einem define) verwendet werden (Stichwort: constant propagation).
    Aber auch wenn das nicht der Fall ist, kann der Compiler die Variable einmal cachen und muss sie dann nicht neu laden, weil sie sich ja eh nicht ändert.

    *Disclaimer wegen absoluter Aussage: Vielleicht gibt es da irgendeine tolle Optimierung, wenn ein Compiler weiß, dass eine Funktion nicht mehr weiter überschrieben wird oder so, aber das wars auch.


  • Mod

    Nathan schrieb:

    const im Gegensatz bietet sehr wohl die Möglichkeit vieler Optimierungen.

    Aber nur im Sinne von Compilezeitkonstanten. So etwas wie

    void foo(int i)
    {
      const int c = 2 * i;
      // ... c wird nie geandert ...
    }
    

    ist im Sinne der Codeerzeugung das gleiche wie

    void foo(int i)
    {
      int c = 2 * i;
      // ... c wird nie geandert ....
    }
    

    . Der Compiler merkt schon von ganz alleine ob und wann sich eine Variable wie aendert. Schliesslich ist das sein Job! Und im Gegenteil kann er sich auf ein const nicht blind verlassen, da er dann auch gegenkontrollieren muesste, ob da nicht irgendwas Verruecktes mit mutable getrieben wird.



  • Also wie nun, ist const bei Methoden und Argumenten nun auch eine Optimierungshilfe oder ist es wirklich nur zur eigenen Sicherheit, so wie bei public, private etc.

    Das sind alles Fragen die mir beim C++ Lernen sofort in den Sinn kommen, aber was gar nicht in den Fachbüchern richtig behandelt wird.


  • Mod

    Objekter schrieb:

    Ist final nicht auch für die Optimierung da?

    final kann zu besserem kompiliertem Code führen, weil Compiler so in die Lage versetzt wird, Aufrufe der virtuellen Funktion ggf. statisch aufzulösen. Praktisch fällt es mir schwer, ein Beispiel zu konstruieren, dass
    1. wesentlich von dieser Optimierung profitiert, und
    2. nicht gleichzeitig laut vernehmlich "Fehldesign!" schreit.

    const versetzt den Compiler nicht in die Lage, bessseren Code zu erzeugen (unter der Voraussetzung, dass die Programme mit oder ohne const ansonsten äquivalent sind).

    Fazit: const und final sind wertvolle Mittel um korrekten Code zu schreiben - und sollten für diesen Zweck eingesetzt werden. Es sind aber keine magischen Programmbeschleuniger.


  • Mod

    const ist primär ein Sicherheitsmechanismus*. Nur im Fall globaler Konstanten kann es zu Optimierungen führen, die ansonsten nicht möglich wären.

    const double PI = 3.0; 
    // kann höchstwahrscheinlich besser optimiert werden als
    double PI = 3.0;
    

    Oft müssen solche Konstanten ja ohnehin const sein, man hat gar keine Wahl:

    const int NUM_QUARKS_PER_BARYON = 3;
    // ...
    class Proton { Quark valence_quarks[NUM_QUARKS_PER_BARYON]; }; // Geht.
    
    int NUM_QUARKS_PER_MESON = 2;
    // ...
    class Kaon { Quark valence_quarks[NUM_QUARKS_PER_MESON]; }; // Geht nicht.
    

    *: Aber wichtig! Spar bloß nicht an der const-correctness, weil sie keine Laufzeitverbesserungen bringt!



  • Ok, danke für die Tipps. Eine letzte Frage hätte ich noch. Ich habe gehört, dass ++i schneller sein soll als i++, weil kein temporäres Objekt erzeugt werden muss, ist das richtig?


  • Mod

    Objekter schrieb:

    Ok, danke für die Tipps. Eine letzte Frage hätte ich noch. Ich habe gehört, dass ++i schneller sein soll als i++, weil kein temporäres Objekt erzeugt werden muss, ist das richtig?

    Es kann schneller sein und wird (uebliche Semantik vorausgesetzt) nie langsamer sein. Wenn einem also der Rueckgabewert egal ist und man nur den Effekt wuenscht, dann macht es Sinn, ++i zu schreiben. Am besten immer, da mit man sich dann dran gewoehnt und gar nicht erst nachdenken braucht, ob es im konkrete Fall ueberhaupt einen Unterschied macht.

    Bei Basisdatentypen kannst du davon ausgehen, dass der Unterschied immer wegoptimiert werden kann. Es kann aber einen echten, messbaren Unterschied machen, wenn man einen eigenen Datentyp mit selbstdefiniertem operator++ vorliegen hat und diese Definition auch noch so komplex (oder vermutlich realistischer: Fuer den Optimierer unsichtbar!) ist, dass der temporaere Wert nicht wegoptimiert werden kann.


Anmelden zum Antworten