C++11 Frage
-
Okay, braucht man doch dann nur um bei einer Klasse den Move Konstruktor sicher aufrufen zu können?
MfG
Edit: Weiß ich nicht

-
Warum definiert man move nicht eigentlich einfach so?
template <typename T> T&& move(T& x) { return static_cast<T&&>(x): }Dass es dann mit RValues nicht funktioniert, interessiert doch keinen.
-
Kellerautomat schrieb:
Warum definiert man move nicht eigentlich einfach so?
template <typename T> T&& move(T& x) { return static_cast<T&&>(x): }Dass es dann mit RValues nicht funktioniert, interessiert doch keinen.
Dann geht aber doch z.B. das hier nicht mehr, oder?
string str = "123"; int z; istringstream(str) >> z;
-
Wo ist hier ein move?
-
Kellerautomat schrieb:
Warum definiert man move nicht eigentlich einfach so?
template <typename T> T&& move(T& x) { return static_cast<T&&>(x): }Dass es dann mit RValues nicht funktioniert, interessiert doch keinen.
Das hatte ich mich auch mal gefragt; denn es ist ja sogar eher schädlich, move auf einem Rvalue aufzurufen, da dadurch copy elision unterdrückt wird. Aber ich kann mir vorstellen, dass man in manch generischem Code nicht immer weiß, ob ein Funktionsaufruf (z.B. eine Iterator-Dereferenzierung) ein Lvalue oder Rvalue-Ausdruck ist. Man würde ein Lvalue erwarten, aber es gibt ja auch Iteratoren von "lazy sequences" die den Wert erst dann ausrechnen, wenn man ihn haben will und das ist dann ggf. ein temporäres Objekt...
-
krümelkacker schrieb:
denn es ist ja sogar eher schädlich, move auf einem Rvalue aufzurufen, da dadurch copy elision unterdrückt wird.
Wie das? Kannst du das näher erläutern?
-
yihaaa schrieb:
Was ist der Unterschied zwischen diesen beiden Funktionen?
void func1( int&& ); // #1 void func2( int& ); // #2Gut, dass der Parameter bei #1 eine "Rvalue-Referenz" und bei #2 eine "Lvalue-Referenz" ist, ist inzwischen klar. Ist die Bedeutung denn eigentlich klar?
Eine wichtige Eigenschaft von Rvalue-Referenzen ist: Sie lassen sich nur so initialisieren, dass sie auf etwas verweisen, für das sich kein anderer mehr interessiert. Und da besteht der große Unterschied zwischen R/Lvalue-Referenzen. Die Funktion #1 darf also einfach das int-Objekt ändern, ohne dass es jemanden stören würde; denn so
int i = 23; func1(i);kann man sie auch gar nicht versehentlich aufrufen. Das gibt nämlich einen Compile-Fehler. func1 kann man aber so aufrufen:
int i = 23; func1(i+1); // Referenz wird sich nur auf ein temporäres Objekt beziehen. func1(std::move(i)) // Referenz wird sich auf i beziehenIm letzten Fall ist das aber wieder OK, weil wir hier explizit durch std::move sagen "der Wert von i interessiert uns nicht mehr".
Praktisch wird das dann hauptsächlich bei Move-Konstruktoren. Die nehmen statt "const ObjektTyp&", wie es ein Kopier-Konstruktor tun würde, eben eine Rvalue-Referenz auf nicht const, "ObjektTyp&&". Und weil diese Referenz sich nur auf etwas beziehen kann, was keinen anderen mehr interessiert, darf der Move-Konstruktor das Quell-Objekt verändern. Bei manchen Typen lohnt sich so etwas, weil man den Move-Ctor sehr effizient implementieren kann. Aber auch an anderen Stellen sind diese Referenzen praktisch. Bei vector wird push_back beispielsweise so überladen:
template<class T, class Alloc> class vector { ... void push_back(T const& x); void push_back(T && x); ... };wobei die erste Version das Objekt x in den Vektor kopiert und die zweite Version versucht, das Objekt in den Vektor zu "moven", indem std::move(x) als Konstruktor-Argument genommen wird. Falls es keinen Move-Konstruktor gibt, wird dann eben dank der Regeln der Überladungsauflösung der Kopier-Konstruktor genommen.
std::move sollte man allerdings nicht dann einsetzen, wenn es überflüssig wäre. Überflüssig wäre es, wenn der Ausdruck sowieso schon ein Rvalue ist oder wenn man eine funktionslokale Variable per return zurück geben will:
int quelle(); ... func1(quelle()); // klappt ohne std::move, quelle() ist schon ein Rvalue ...string dings() { string x; ... return x; // std::move ist hier nicht nötig sondern eher schädlich (*) }(* weil dadurch die "return value optimization" ausgeschaltet würde)
kk
-
krümelkacker schrieb:
std::move sollte man allerdings nicht dann einsetzen, wenn es überflüssig wäre.
Fehlt mir immernoch die Begründung zu. Wird ein move eines rvalues nicht so oder so zum noop?
(* weil dadurch die "return value optimization" ausgeschaltet würde)
Sicher? Kommt die (n)rvo nicht spät genug, um den move, der nichts tut, schon garnicht mehr zu sehen?
-
pumuckl schrieb:
krümelkacker schrieb:
denn es ist ja sogar eher schädlich, move auf einem Rvalue aufzurufen, da dadurch copy elision unterdrückt wird.
Wie das? Kannst du das näher erläutern?
RVO setzt voraus, dass das zu initialisierende Objekt durch ein temporäres Objekt (d.h. durch einen Ausdruck, der das temporäre Objekt selbst erzeugt) initialisiert wird, die Rückgabe von std::move ist aber kein solches.
Praktisch sehe ich kein so großes Problem. Die Fehlerquote durch überflüssiges move, wo sonst NRVO möglich wäre, halte ich für größer (weil diese Moveregel neu und nicht offensichtlich ist). Ich könnte mir vorstellen (mir fällt aber gerade kein sinnvolles Beispiel ein), dass es in generischem Code mitunter nicht immer sofort offenbar ist, ob ein potentielles move-Argument bereits ein temporäres Objekt ist oder nicht, so dass ein move, dass mit allem zurechtkommt, zumindest bequem ist (nat. gibt Möglichkeiten, immer das Optimale zu tun).
Solche überflüssigen move-Aufrufe lassen sich ja wahrscheinlich leicht automatisiert finden: einfach eine move-Überladung in std einfügen, die für Mehrdeutigkeiten sorgt:
namespace std { template <typename T> typename std::enable_if<!std::is_reference<T>::value, void>::type move(T&& x); }
-
pumuckl schrieb:
(* weil dadurch die "return value optimization" ausgeschaltet würde)
Sicher?
Ja.
pumuckl schrieb:
Kommt die (n)rvo nicht spät genug, um den move, der nichts tut, schon garnicht mehr zu sehen?
Copy elision ist nur dann erlaubt, wenn der "Quellausdruck" sich direkt auf ein temporäres Objekt (generell) oder ein funktionslokales Objekt (im Falle eines return-Statements) bezieht. Ein std::move() dazwischen liefert dir nur eine Referenz auf etwas.
Eine richtig blöde Idee ist dann so etwas:
string dings(); int main() { for (char c : move(dings())) { cout << c; } cout << endl; }denn da rufste auf einmal undefiniertes Verhalten hervor, weil das temporäre Objekt, was dings zurück gibt, nicht lang genug am Leben bleibt. Die Regel, die die Lebenszeit eines temporären Objekts verlängert, greift auch nur dann, falls man ein "pure rvalue" (*) dort stehen hat. Der move-Ausdruck ist aber ein Xvalue.
(* ein static_cast<ObjectType&&>(x) geht sogar auch noch, meines Wissens nach, was ja ein Xvalue ist, aber eben kein Xvalue eines Funktionsaufrufs )