Konstante Getter-Funktionen?
-
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) movegcc 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...

-
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 badHier 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 badcamper 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.
-
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 badInteressant.
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 badHier 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 badOffenbar 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.
-
pumuckl schrieb:
copy good consume good copy bad consume bad copy good copy good consume good copy bad copy bad consume badOffenbar 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.