Konstante Getter-Funktionen?



  • Wenn du dich nicht mit C++11 Feinheiten rumschlagen willst, UND diverse (mMn. sehr weit hergeholte) Programmierfehler (siehe Mechanics' Beitrag) verhindern willst, dann wenn

    1. Die Funktion einen Wert "by value" zurückgibt
      UND
    2. Der Returnwert einen "user defined type" hat (user defined type = class/struct)

    Natürlich gibt es ein paar Ausnahmen, aber wenn du denen begegnest stolperst du eh von selbst drüber, weil etwas nicht mehr geht was ganz klar gehen sollte.

    Heisst bei int , float , char etc. sowie sämtlichen Zeigern macht ein top-level const keinen Sinn. Es schadet nicht, aber es bringt auch nix. Weil nämlich C++ von sich aus schon die besagten weit hergeholten Programmierfehler verhindert. Wenn du sehr auf Konsistenz Wert legst kannst du es natürlich auch gerne bei solchen Typen schreiben. Sonst würde ich sagen lass es.

    Und zum Thema Zeiger noch um einem möglichen Misverständnis vorzugreifen:

    MeineKlasse* const MeineFunktion(); // <-- DAS ist ein top-level const (Zeiger ist const)
    MeineKlasse const* MeineFunktion(); // <-- das ist KEIN top-level const (MeineKlasse Objekt ist const)
    
    const MeineKlasse* MeineFunktion(); // <-- das ist AUCH KEIN top-level const, ist das selbe wie MeineKlasse const*
    

  • Mod

    SeppJ schrieb:

    Clundsch schrieb:

    also bei mir funktioniert

    int const a() {}
    

    und

    const int a() {}
    

    Das ist auch beides exakt das gleiche

    Kleine Anmerkung, das ist nicht ganz exakt das Gleiche, sondern unterschiedliche Funktionen, deren Typ sich unterscheidet. Die Eleminierung von top-level cv-Qualikationenen, ebenso wie die Substitution von Array- durch Zeigerdeklarationen bzw. von Funktionen durch Funktionszeiger findet nur für die Parameter statt.
    Funktionen, die Arrays oder Funktionen zurückgeben gibt es nicht, solche Deklarationen sind ill-formed. Wer Spaß daran hat, kann aber gerne seine void-Funktionen durch const volatile void-Funktionen ersetzen.
    Diese cv-Qualifikation ändert allerdings nichts am Typ eines Ausdrucks, der einen Funktionsaufruf darstellt, wenn der Rückgabetyp ein Skalar ist.

    1. Darf eine T&& Referenz wirklich an einen Returnwert vom Typ "T const" gebunden werden? (Spricht: funktioniert move noch wenn man den Returnwert top-level const macht?)

    nein. Etwas anderes gilt nat. für deduzierbare Templateparameter, ein T&& kann auch als T=const U deduziert werden. Der gewöhnliche Move-Konstruktor/Zuweisungsoperator nimmt aber nur modifizierbare rvalues. in jedem Fall kommt es auf den Typ des Ausdrucks an, der rechts steht, und der ist für Skalare stets unqualifiziert.



  • camper schrieb:

    1. Darf eine T&& Referenz wirklich an einen Returnwert vom Typ "T const" gebunden werden? (Spricht: funktioniert move noch wenn man den Returnwert top-level const macht?)

    nein. Etwas anderes gilt nat. für deduzierbare Templateparameter, ein T&& kann auch als T=const U deduziert werden. Der gewöhnliche Move-Konstruktor/Zuweisungsoperator nimmt aber nur modifizierbare rvalues. in jedem Fall kommt es auf den Typ des Ausdrucks an, der rechts steht, und der ist für Skalare stets unqualifiziert.

    OK, Skalare mal beiseite...

    Wenn ein Move-Konstruktor nicht mit einer "const UDT fun();" funktioniert, dann ist
    a) Clang 3.0 verbuggt (MSVC auch, aber OK, ... QEE)
    und
    b) top-level const im Returntyp mit UDTs BÖSE weil es Move verhindert

    Oder übersehe ich da jetzt was (bezüglich (b)).

    ps: ich verstehe deine Antwort bezüglich "const int a()" vs "int const a()" nicht. Ob const vor oder hinter dem int ist doch egal, ist doch in beiden Fällen top level const... nicht?



  • hustbaer schrieb:

    Wenn ein Move-Konstruktor nicht mit einer "const UDT fun();" funktioniert, dann ist
    a) Clang 3.0 verbuggt (MSVC auch, aber OK, ... QEE)

    Nein, das ist schon richtig so. Ein Move Konstruktor nimmt normalerweise eine UDT&& und keine const UDT&&. Es ist ja normalerweise gerade erst der Zweck eines Move Konstruktors, das Objekt, von dem gemoved wird, zu ändern...

    hustbaer schrieb:

    b) top-level const im Returntyp mit UDTs BÖSE weil es Move verhindert

    top-level const im Returntyp ist nicht prinzipiell böse, aber sowas wie const int als Return Type ist wohl sinnlos. Die einzig mir bekannte sinnvolle Anwendung von top-level const auf einem Returntype, wenn ein Objekt by Value returned wird, ist operator overloading.

    hustbaer schrieb:

    ps: ich verstehe deine Antwort bezüglich "const int a()" vs "int const a()" nicht. Ob const vor oder hinter dem int ist doch egal, ist doch in beiden Fällen top level const... nicht?

    Ich glaub, da hat er einfach einmal das const übersehen 😉


  • Mod

    dot schrieb:

    Ich glaub, da hat er einfach einmal das const übersehen 😉

    Err ja, hatte da sowas wie

    int a();
    const int a();
    

    gelesen.



  • hustbaer schrieb:

    Wenn du dich nicht mit C++11 Feinheiten rumschlagen willst, UND diverse (mMn. sehr weit hergeholte) Programmierfehler (siehe Mechanics' Beitrag) verhindern willst, dann wenn

    1. Die Funktion einen Wert "by value" zurückgibt
      UND
    2. Der Returnwert einen "user defined type" hat (user defined type = class/struct)

    Natürlich gibt es ein paar Ausnahmen, aber wenn du denen begegnest stolperst du eh von selbst drüber, weil etwas nicht mehr geht was ganz klar gehen sollte.

    Heisst bei int , float , char etc. sowie sämtlichen Zeigern macht ein top-level const keinen Sinn. Es schadet nicht, aber es bringt auch nix. Weil nämlich C++ von sich aus schon die besagten weit hergeholten Programmierfehler verhindert. Wenn du sehr auf Konsistenz Wert legst kannst du es natürlich auch gerne bei solchen Typen schreiben. Sonst würde ich sagen lass es.

    Und wenn ich mich mit C++11 Feinheiten herumschlagen will? 😃



  • SeppJ schrieb:

    int a() const { return a_; }
    

    Bei dir ist der zurück gegebene Wert konstant (was reichlich sinnlos ist, daher die Warnung), hier ist a() konstant, wodurch a() auch auf konstante A angewendet werden kann, da dieses A garantiert nicht verändert werden kann (Ausnahmen durch mutable mal ausgenommen).

    Das stimmt so, aber das Hauptziel hier ist wohl eher anzuzeigen, dass das Objekt selber bei der Ausführung von a() nicht verändert wird, was bei einem getter ja wohl immer der Fall sein sollte. Oder anders: alle Methoden, die das Objekt (sprich darauf wo this zeigt) nicht verändern, sollten als const deklariert werden. Dann dürfen allerdings auch nur aus dieser Methode heraus als const deklarierte Methoden aufgerufen werden. Das überprüft dann der Compiler für dich.



  • dot schrieb:

    hustbaer schrieb:

    Wenn ein Move-Konstruktor nicht mit einer "const UDT fun();" funktioniert, dann ist
    a) Clang 3.0 verbuggt (MSVC auch, aber OK, ... QEE)

    Nein, das ist schon richtig so. Ein Move Konstruktor nimmt normalerweise eine UDT&& und keine const UDT&&. Es ist ja normalerweise gerade erst der Zweck eines Move Konstruktors, das Objekt, von dem gemoved wird, zu ändern...

    Clang 3.0 bindet einen const UDT Returnwert direkt an UDT&& Parameter ( UDT = konkreter Typ, kein Template-Parameter!).
    Nach dem was camper geschrieben hat dürfte er das ja nicht. Oder doch?

    Also konkret, im Beispiel unten macht Clang 3.0 2x move und nicht 1x move und 1x copy.
    Es sei denn ich hatte in meinem Test-Code nen Fehler -- muss das heute Abend nochmal genauer untersuchen.

    dot schrieb:

    hustbaer schrieb:

    b) top-level const im Returntyp mit UDTs BÖSE weil es Move verhindert

    top-level const im Returntyp ist nicht prinzipiell böse, aber sowas wie const int als Return Type ist wohl sinnlos. Die einzig mir bekannte sinnvolle Anwendung von top-level const auf einem Returntype, wenn ein Objekt by Value returned wird, ist operator overloading.

    Also wenn ich euch richtig verstanden habe, dann...

    std::string good();
    std::string const bad();
    
    std::string pipe(std::string s) { return s; }
    void consumer(std::string s) { /* konsumiert irgendwie s - ist ja egal wie */ }
    
    void test()
    {
        consumer(pipe(good())); // 1x move
        consumer(pipe(bad())); // 1x copy
    }
    

    ...und das ist für mich böse, weil ... wieso move verhindern?

    Wenn man good() = "foo"; oder ähnliches verhindern will ohne move zu verhindern, dann muss std::string seinen operator = doch bloss "&" qualifizieren:

    template <...>
    class basic_string
    {
    // ...
        basic_string& operator = (basic_string other) &; // tadaa
    };
    

    const im Returntyp ist dabei nicht nötig.

    Daher auch die Frage ob die Signatur eines compilergenerierten operator = "&" oder "&&" qualifiziert ist.

    dot schrieb:

    hustbaer schrieb:

    ps: ich verstehe deine Antwort bezüglich "const int a()" vs "int const a()" nicht. Ob const vor oder hinter dem int ist doch egal, ist doch in beiden Fällen top level const... nicht?

    Ich glaub, da hat er einfach einmal das const übersehen 😉

    Nachdem camper SO selten Fehler macht dachte ich mir ich frag' sicherheitshalber lieber 🙂



  • ichnicht schrieb:

    hustbaer schrieb:

    Wenn du dich nicht mit C++11 Feinheiten rumschlagen willst, UND diverse (mMn. sehr weit hergeholte) Programmierfehler (siehe Mechanics' Beitrag) verhindern willst, dann wenn

    1. Die Funktion einen Wert "by value" zurückgibt
      UND
    2. Der Returnwert einen "user defined type" hat (user defined type = class/struct)

    Natürlich gibt es ein paar Ausnahmen, aber wenn du denen begegnest stolperst du eh von selbst drüber, weil etwas nicht mehr geht was ganz klar gehen sollte.

    Heisst bei int , float , char etc. sowie sämtlichen Zeigern macht ein top-level const keinen Sinn. Es schadet nicht, aber es bringt auch nix. Weil nämlich C++ von sich aus schon die besagten weit hergeholten Programmierfehler verhindert. Wenn du sehr auf Konsistenz Wert legst kannst du es natürlich auch gerne bei solchen Typen schreiben. Sonst würde ich sagen lass es.

    Und wenn ich mich mit C++11 Feinheiten herumschlagen will? 😃

    Dann schreib lieber deine Klassen so, dass der Assignment-Operator nicht auf Rvalues funktioniert (siehe Beispiel mit basic_string oben).
    Das entspricht mMn. nämlich am ehesten dem was eingebaute Typen machen.



  • Ich hab grade mal mit r-values gespielt und das Beispiel von oben etwas verändert. Das Ergebnis verwundert mich:

    #include <iostream>
    #include <cstring>
    using namespace std;
    
    struct X {
      X(char const* str) : ptr(0) {ptr = new char[strlen(str)+1]; strcpy(ptr, str);}
      X(X&& other)      : ptr(other.ptr) {other.ptr = 0; cout << "move " << ptr << ' ';}
      X(X const& other) : ptr(0)         {ptr = new char[strlen(other.ptr)+1]; strcpy(ptr, other.ptr); cout << "copy " << ptr << ' ';}
      ~X() {delete ptr;}
    
      char* ptr;
    };
    
    X good() {X x("good"); return x;}
    X const bad() {X x("bad"); return x;}
    
    X pipe(X x) { return x; }
    void consumer(X x) { std::cout << "consume " << x.ptr << '\n'; }
    
    int main()
    {
        consumer(good());      //erwartet: max. 1 move
        consumer(bad());       //erwartet: max. 1 move
        consumer(pipe(good()));//erwartet: max. 2 move
        consumer(pipe(bad())); //erwartet: max. 1 move, 1 copy
    }
    

    Ergebnis gcc-4.5.1: (ideone)

    consume good
    consume bad
    copy good consume good
    copy bad consume bad
    

    wieso kopiert er den "good"? sollte doch perfekt movebar sein? Dass es überall ein move weniger ist dürfte (N)RVO sein (habs auch mit unbenannten RV's in good() und bad() ausprobiert.

    Ergebnis VS10 (debug):

    move good consume good
    move bad consume bad
    move good move good consume good
    move bad move bad consume bad
    

    offenbar bindet der schamlos rrefs an const r-values. Was sagt der Standard dazu?


  • Mod

    Ein move ist auch innerhalb der Funktionen beim return denkbar (in pipe sogar erforderlich) während die Kopie möglicherweise ausgelassen wird.
    Eigentlich:

    //return    call     return         call
        consumer(good());      //erwartet:    move      move
        consumer(bad());       //erwartet:    move      copy
        consumer(pipe(good()));//erwartet:    move      move     move (immer)   move
        consumer(pipe(bad())); //erwartet:    move      copy     move (immer)   move
    

    gcc 4.5.1 kennt die move-Regel beim lvalue-return noch nicht, gcc-4.6.3 macht das dann richtig:

    consume good
    consume bad
    move good consume good
    move bad consume bad
    


  • Klitzekleine Nachlässigkeit...

    ~X() {delete[] ptr;}
    

    Wenn man hier mitliest ist man ganz versessen darauf new/delete Fehler zu erspähen... 😉


  • Mod

    Wie schon ausgeführt, kann der obige Code nicht schlüssig demonstrieren, dass msvc konstante Objekte on modifizierbare rvalue-Referenzen bindet. Weil nicht klar wird, welche Copy/Move-Operationen ausgelassen werden. Eine kleine Modifikation ist erforderlich:

    #include <iostream>
    #include <cstring>
    using namespace std;
    
    struct X {
      X(char const* str) : ptr(0) {ptr = new char[strlen(str)+1]; strcpy(ptr, str);}
      X(X&& other)      : ptr(other.ptr) {other.ptr = 0; cout << "move " << ptr << ' ';}
      X(X const& other) : ptr(0)         {ptr = new char[strlen(other.ptr)+1]; strcpy(ptr, other.ptr); cout << "copy " << ptr << ' ';}
      ~X() {delete []ptr;}
    
      char* ptr;
    };
    
    X good() { X x("good"); return *&x;}
    X const bad() { X x("bad"); return *&x;}
    
    X pipe(X x) { return *&x; }
    void consumer(X x) { std::cout << "consume " << x.ptr << '\n'; }
    
    int main()
    {
        // * = copy/move can be elided        return    call     return   call
        consumer(good());      //erwartet:    copy      move*
        consumer(bad());       //erwartet:    copy      copy*
        consumer(pipe(good()));//erwartet:    copy      move*    copy     move*
        consumer(pipe(bad())); //erwartet:    copy      copy*    copy     move*
    }
    

    Hier muss beim return in jedem Fall eine Copy gemacht werden, jeder zusätzliche Konstruktoraufruf würde also vom Funktionsaufruf stammen.
    gcc ist hier mal wieder zu aggresiv:

    consume good
    consume bad
    move good consume good
    move bad consume bad
    

    Hier wird also von gcc NRVO angewendet, obwohl das nicht zulässig ist. Also lieber so:

    #include <iostream>
    #include <cstring>
    using namespace std;
    
    struct X {
      X(char const* str) : ptr(str) {}
      X(X&& other)      : ptr(other.ptr) {cout << "move " << ptr << ' ';}
      X(X const& other) : ptr(other.ptr) {cout << "copy " << ptr << ' ';}
      ~X() {}
    
      char* ptr;
    };
    
    X a("good"), b("bad");
    
    X good() { return a;}
    X const bad() { return b;}
    
    X pipe(X x) { return x.ptr==a.ptr?a:b; }
    void consumer(X x) { std::cout << "consume " << x.ptr << '\n'; }
    
    int main()
    {
        // * = copy/move can be elided        return    call     return   call
        consumer(good());      //erwartet:    copy      move*
        consumer(bad());       //erwartet:    copy      copy*
        consumer(pipe(good()));//erwartet:    copy      move*    copy     move*
        consumer(pipe(bad())); //erwartet:    copy      copy*    copy     move*
    }
    

    jetzt erhalte ich

    copy good consume good
    copy bad consume bad
    copy good copy good consume good
    copy bad copy bad consume bad
    


  • camper schrieb:

    X good() { X x("good"); return *&x;}
    X const bad() { X x("bad"); return *&x;}
     
    X pipe(X x) { return *&x; }
    

    Hier muss beim return in jedem Fall eine Copy gemacht werden, jeder zusätzliche Konstruktoraufruf würde also vom Funktionsaufruf stammen.
    gcc ist hier mal wieder zu aggresiv:

    MSVC:

    move good consume good
    move bad consume bad
    move good move good consume good
    move bad move bad consume bad
    

    camper schrieb:

    Also lieber so:

    #include <iostream>
    #include <cstring>
    using namespace std;
     
    struct X {
      X(char const* str) : ptr(str) {}
      X(X&& other)      : ptr(other.ptr) {cout << "move " << ptr << ' ';}
      X(X const& other) : ptr(other.ptr) {cout << "copy " << ptr << ' ';}
      ~X() {}
     
      char* ptr;
    };
    
    main.cpp(6): error C2440: 'initializing' : cannot convert from 'const char *' to 'char *'
    

    😉 Hatte das auch erst so versucht, dann doch aber mit strcpy & co gearbeitet. Mit der anderen Implementierung von X wie vorher und good()/bad()/pipe() wie bei dir bekomme ich das selbe: 1-2x copy, kein move.


  • Mod

    pumuckl schrieb:

    camper schrieb:

    X good() { X x("good"); return *&x;}
    X const bad() { X x("bad"); return *&x;}
     
    X pipe(X x) { return *&x; }
    

    Hier muss beim return in jedem Fall eine Copy gemacht werden, jeder zusätzliche Konstruktoraufruf würde also vom Funktionsaufruf stammen.
    gcc ist hier mal wieder zu aggresiv:

    MSVC:

    move good consume good
    move bad consume bad
    move good move good consume good
    move bad move bad consume bad
    

    Interessant.
    Also nochmal Faktencheck:

    n3337 12.8/31 schrieb:

    When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class
    object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases,
    the implementation treats the source and target of the omitted copy/move operation as simply two different
    ways of referring to the same object, and the destruction of that object occurs at the later of the times
    when the two objects would have been destroyed without the optimization.123 This elision of copy/move
    operations, called copy elision, is permitted in the following circumstances (which may be combined to
    eliminate multiple copies):
    123) Because only one object is destroyed instead of two, and one copy/move constructor is not executed, there is still one
    object destroyed for each one constructed.
    — in a return statement in a function with a class return type, when the expression is the name of a
    non-volatile automatic object (other than a function or catch-clause parameter) with the same cvunqualified
    type as the function return type, the copy/move operation can be omitted by constructing
    the automatic object directly into the function’s return value
    — in a throw-expression, [...]
    — when a temporary class object that has not been bound to a reference (12.2) would be copied/moved
    to a class object with the same cv-unqualified type, the copy/move operation can be omitted by
    constructing the temporary object directly into the target of the omitted copy/move
    — when the exception-declaration of an exception handler (Clause 15) declares [...]

    in

    return *&x;
    

    liegt kein temporäres Objekt vor, also kommt der 3. Anstrich nicht in Frage.
    *&x ist auch nicht der Name eines lokalen Objektes, sondern schlicht ein anderer Ausdruck, der nur zufällig auf ein solches Objekt verweist. Der Standard gibt gcc also nicht das Recht, hier die Kopie auszulassen.

    n3337 12.8/32 schrieb:

    When the criteria for elision of a copy operation are met or would be met save for the fact that the source
    object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to
    select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload
    resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to
    the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an
    lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will
    occur. It determines the constructor to be called if elision is not performed, and the selected constructor
    must be accessible even if the call is elided. —end note ]

    Da schon geklärt wurde, dass Copy-Elision nicht ausgeführt werden kann, und der Grund dafür in der Form des Ausdrucks zu suchen ist und nicht etwa in der Tatsache, dass ein Funktionsargument zurückgegeben wird, kommt diese Optimierung auch nicht in Frage. Es muss kopiert werden.
    clang macht es übrigens richtig.

    Wie verhält sich msvc, bei der Variante mit globalen Objekten?

    pumuckl schrieb:

    main.cpp(6): error C2440: 'initializing' : cannot convert from 'const char *' to 'char *'
    

    Muss nat. const char* hin, hatte ich so getestet, nur dann hier beim Posten vergessen, zu ändern.



  • camper schrieb:

    Wie schon ausgeführt, kann der obige Code nicht schlüssig demonstrieren, dass msvc konstante Objekte on modifizierbare rvalue-Referenzen bindet. Weil nicht klar wird, welche Copy/Move-Operationen ausgelassen werden.

    Du hast (wie meistens) vollkommen Recht - und ich habe MSVC anscheinend zu unrecht beschuldigt. Und Clang vermutlich auch.

    camper schrieb:

    Eine kleine Modifikation ist erforderlich:

    (...)

    Hier muss beim return in jedem Fall eine Copy gemacht werden, jeder zusätzliche Konstruktoraufruf würde also vom Funktionsaufruf stammen.
    gcc ist hier mal wieder zu aggresiv:

    consume good
    consume bad
    move good consume good
    move bad consume bad
    

    Hier wird also von gcc NRVO angewendet, obwohl das nicht zulässig ist.

    Bekomm' ich mit MSVC das selbe Ergebnis.

    Ich hab meinen Test jetzt nochmal umgeschrieben:

    #include <iostream>
    #include <utility>
    
    using namespace std;
    
    struct foo {
    	explicit foo(char const* s) : m_str(s) { cout << "init " << m_str << "\n"; }
    	foo(foo const& other)  : m_str(other.m_str) { cout << "copy " << m_str << "\n"; }
    	foo(foo&& other) : m_str(other.m_str) { cout << "move " << m_str << "\n"; }
    	~foo() { cout << "destroy " << m_str << "\n"; }
    
    	foo& operator = (foo const& other) { m_str = other.m_str; cout << "copy-assign " << m_str << "\n"; return *this; }
    	foo& operator = (foo&& other) { m_str = other.m_str; cout << "move-assign " << m_str << "\n"; return *this; }
    
    private:
    	char const* m_str;
    };
    
    foo good() { return foo("good"); }
    foo const bad() { return foo("bad"); }
    
    void consume(foo) { }
    
    template <class T> void consume2(T&& t) {
    	consume(forward<T>(t));
    }
    
    int main()
    {
    	consume2(good());
    	cout << "\n";
    	consume2(bad());
    //	cout << "\n";
    //	good() = foo("nicht so gut");
    }
    

    Damit bekomme ich von MSVC 11...

    init good
    move good
    destroy good
    destroy good
    
    init bad
    copy bad
    destroy bad
    destroy bad
    

    ...was ich für vernünftig halte.

    (EDIT: GCC 4.5.1 macht das selbe: http://ideone.com/v4fkC )

    Ich schliesse daraus: Returntyp mit top-level const verhindert Zuweisung, aber auch (in manchen Fällen) move.

    Den Assignment-Operator auf Lvalues einzuschränken ist mMn. ganz klar die bessere Lösung.
    Einerseits muss man es nur 1x pro Klasse machen, und nicht pro Funktion die die Klasse als Returntyp verwendet. Und andrerseits verhindert es die unerwünschte Zuweisung, lässt uns aber das erwünschte move.

    Dummerweise kann MSVC 11 das noch nicht.

    BTW: gibt's ne Seite ala ideone wo man Clang 3.0, Clang 3.1, GCC 4.6 und/oder GCC 4.7 ausprobieren kann (inklusive Code Ausführen)?



  • camper schrieb:

    Wie verhält sich msvc, bei der Variante mit globalen Objekten?

    Die globalen Objekte, mit dem char const* member, ergeben folgendes:

    copy good consume good
    copy bad consume bad
    copy good copy good consume good
    copy bad copy bad consume bad
    

    Offenbar ist MSVC wie ich der Meinung dass es ohne wirkliche Ressourcen keinen Grund für moves gibt 😉 Das war der Grund, warum ich mit Allokation und strcpy etc gearbeitet hatte...



  • pumuckl schrieb:

    Offenbar ist MSVC wie ich der Meinung dass es ohne wirkliche Ressourcen keinen Grund für moves gibt 😉 Das war der Grund, warum ich mit Allokation und strcpy etc gearbeitet hatte...

    Glaub' ich nicht.
    Beobachtbare Seiteneffekte sind beobachtbare Seiteneffekte - ob das jetzt die Ausgabe von Strings oder new/delete sind muss egal sein.


  • Mod

    pumuckl schrieb:

    copy good consume good
    copy bad consume bad
    copy good copy good consume good
    copy bad copy bad consume bad
    

    Offenbar ist MSVC wie ich der Meinung dass es ohne wirkliche Ressourcen keinen Grund für moves gibt 😉 Das war der Grund, warum ich mit Allokation und strcpy etc gearbeitet hatte...

    Das zeigt eigentlich nur, dass der Compiler RVO immer einsetzt, wo es möglich ist - das ist zu erwarten, denn diese Optimierung ist einfacher (sie stellt keine zusätzlichen technischen Bedingungen an den Kontext), und Compiler machen das schon seit 10 Jahren zuverlässig.


Anmelden zum Antworten