Frage zu RValue-Referenzen und Optimierung
-
Hallo,
ich habe folgendes Codebeispiel:#include <vector> #include <string> struct Data { Data(std::string string, int integer) : str(string), i(integer){}; std::string str; int i; }; class Second { public: Second(Data&& data_) : data(data_){}; private: Data data; }; class First { public: First(Data&& data) : second(std::move(data)){}; private: Second second; }; int main() { First test(Data("test",42)); return 0; }Nun stellen sich mir da zwei Fragen.
1. Sind hier RValue-Referenzen sinnvoll? Reichen hier nicht auch normale Referenzen zur Optimierung? Und wo ist dann das wirkliche Einstatzgebiet von RValue-Referenzen?
2. Wenn ich bei meinem Compiler (MSVC 10) die Optmierung ausschalte ist die Disassembly wesentlich kleiner, als wenn ich das O2-Flag drin habe. Das ganze sieht auf den ersten Blick dann erst mal schneller aus. Wieso ist das so?
Hier mal die Disassembly:; Ohne Optimierung 002616E0 push 2Ah 002616E2 sub esp,20h 002616E5 mov ecx,esp 002616E7 mov dword ptr [ebp-110h],esp 002616ED push offset string "test" (268830h) 002616F2 call std::basic_string<char,std::char_traits<char>,std::allocator<char> >::basic_string<char,std::char_traits<char>,std::allocator<char> > (2612DAh) 002616F7 mov dword ptr [ebp-144h],eax 002616FD lea ecx,[ebp-13Ch] 00261703 call Data::Data (261019h) 00261708 mov dword ptr [ebp-148h],eax 0026170E mov eax,dword ptr [ebp-148h] 00261714 mov dword ptr [ebp-14Ch],eax 0026171A mov dword ptr [ebp-4],0 00261721 mov ecx,dword ptr [ebp-14Ch] 00261727 push ecx 00261728 lea ecx,[ebp-38h] 0026172B call Parent::Parent (261177h) 00261730 mov byte ptr [ebp-4],2 00261734 lea ecx,[ebp-13Ch] 0026173A call Data::~Data (261145h) ; Mit O2 optimiert 0001107A xor ebx,ebx 0001107C lea edi,[ebx+4] 0001107F lea esi,[esp+14h] 00011083 mov dword ptr [esp+28h],0Fh 0001108B mov dword ptr [esp+24h],ebx 0001108F mov byte ptr [esp+14h],bl 00011093 call std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign (11280h) 00011098 mov eax,esi 0001109A mov dword ptr [esp+88h],ebx 000110A1 push eax 000110A2 or eax,0FFFFFFFFh 000110A5 lea ecx,[esp+3Ch] 000110A9 mov dword ptr [esp+50h],0Fh 000110B1 mov dword ptr [esp+4Ch],ebx 000110B5 mov byte ptr [esp+3Ch],bl 000110B9 call std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign (111A0h) 000110BE mov edi,dword ptr [__imp_operator delete (120BCh)] 000110C4 mov esi,10h 000110C9 mov dword ptr [esp+54h],2Ah 000110D1 cmp dword ptr [esp+28h],esi 000110D5 jb wmain+0A1h (110E1h) 000110D7 mov ecx,dword ptr [esp+14h] 000110DB push ecx 000110DC call edi 000110DE add esp,4 000110E1 xor eax,eax 000110E3 mov ecx,0Fh 000110E8 mov dword ptr [esp+28h],ecx 000110EC mov dword ptr [esp+24h],eax 000110F0 mov byte ptr [esp+14h],al 000110F4 mov dword ptr [esp+88h],1 000110FF lea edx,[esp+38h] 00011103 mov dword ptr [esp+6Ch],ecx 00011107 mov dword ptr [esp+68h],eax 0001110B mov byte ptr [esp+58h],al 0001110F push edx 00011110 or eax,0FFFFFFFFh 00011113 xor ebx,ebx 00011115 lea ecx,[esp+5Ch] 00011119 call std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign (111A0h) 0001111E mov eax,dword ptr [esp+54h] 00011122 mov dword ptr [esp+74h],eax 00011126 cmp dword ptr [esp+4Ch],esi 0001112A jb wmain+0F6h (11136h) 0001112C mov ecx,dword ptr [esp+38h] 00011130 push ecx 00011131 call edi 00011133 add esp,4
-
Bubiboy schrieb:
1. Sind hier RValue-Referenzen sinnvoll? Reichen hier nicht auch normale Referenzen zur Optimierung? Und wo ist dann das wirkliche Einstatzgebiet von RValue-Referenzen?
Data zwingt dich immer zu kopieren, da hilft garnichts. Wenn dein Code sagt: A muss immer kopiert werden, dann kannst das nicht umgehen und musst A immer kopieren.
rvalue Referenzen ermoeglichen dir zu sagen: A muss nicht kopiert werden.
2. Wenn ich bei meinem Compiler (MSVC 10) die Optmierung ausschalte ist die Disassembly wesentlich kleiner, als wenn ich das O2-Flag drin habe. Das ganze sieht auf den ersten Blick dann erst mal schneller aus. Wieso ist das so?
Weil du mit O2 inlining aktiviert hast. Dadurch hast du idR laengeren aber schnelleren Code.
-
Shade Of Mine schrieb:
Data zwingt dich immer zu kopieren, da hilft garnichts.
Wieso das? Wenn ich das Ganze richtig verstanden habe, kann der Compiler im Fall von
Dataeinen impliziten Move-Konstruktor und Move-Zuweisungsoperator generieren. Jetzt mal unabhängig vom Rest des Codes.
-
Ich glaube da fehlen zumindest zwei std::move.
1:Second(Data&& data_) : data(std::move(data_)){};
2:Data(std::string string, int integer) : str(std::move(string)), i(integer){};Bei 1 deswegen, weil das Ding nen Namen hat, und was nen Namen hat wird nimmer als Rvalue verwendet (auch wenn es eine Rvalue Referenz ist).
Bei 2 deswegen, weil der Compiler ja nicht wissen kann, dass du den übergebenen Parameter "kaputtmachen" willst (bzw. auch ganz allgemein deswegen, weil das Teil nen Namen hat).
Das Move "in" den Parameter sollte noch gehen, das Move vom Parameter ins Member aber nicht (also nicht ohne std::move).
Wobei ich dazusagen muss dass ich mir Rvalue Referenzen auch nur am Rande angesehen habe, und noch nirgends wirklich verwendet (wir verwenden noch MSVC 2005 in der Firma)!
Eine Bestätigung/Korrektur eines "Wissenden" wäre also fein

-
@hustbaer: korrekt. Alles was einen Namen hat ist ein LValue, und in beiden Fällen kann gemoved werden.
-
Sind hier RValue-Referenzen sinnvoll? Geht so. Jedenfalls nicht so, wie sie aktuell angewendet wurden. Man kann's auch ganz ohne machen. Warum auch nicht?
#include <vector> #include <string> #include <utility> struct Data { std::string str; int i; }; class Second { public: Second(Data d) : data(std::move(d)){} private: Data data; }; class First { public: First(Data d) : second(std::move(d)){} private: Second second; }; int main() { First test(Data{"test",42}); return 0; }(ungetestet)
Wenn ich hier keinen Fehler gemacht habe, wird hier nichts unnötig kopiert, nur ein paar mal "gemovet".Rvalue-Referenzen bzgl Move-Semantik sind dazu da, den Move-Constructor schreiben zu können. Sonst sind das stinknormale Referenzen. Es gibt nur zwei Sonderheiten (und eine dritte, dich ich verschweige, weil ich Dich nicht durcheinander bringen will): Du kannst eine Rvalue-Referenz nur mit etwas Initialisieren, was sich entweder auf ein temporäres Objekt oder auf ein anderes Objekt beziehen wird, dessen Zustand verändert werden darf, ohne dass das jemanden stören würde. Mit std::move kann man so etwas zB ausdrücken, sollte man aber auch nur machen, wenn es nicht sowieso schon überflüssig wäre. Der zweite Unterschied liegt bei einer Rvalue-Referenz als Rückgabe-Typ einer Funktion. Das sollte man zwar bei eigenen Funktionen vermeiden (!!!), aber du solltest wissen, dass ein solcher Funktionsaufruf ein Rvalue-Ausdruck und kein Lvalue-Ausdruck darstellt. std::move und std::forward nutzen dies aus.
Nachtrag: Data wird hier jeweils "per Wert" an die Konstruktoren übergeben. Je nachdem, was Du für eine Quelle hast (ist das Argument ein Lvalue oder Rvalue?) wird hier entweder der Kopier- oder der Move-Konstruktor ausgewählt. Der Parameter 'd' ist ein Lvalue-Ausdruck, der aber später nicht mehr benötigt wird. Daher wird der nächste Konstruktor mit std::move(d) als Argument aufgerufen, was eine unnötige Kopie verhindert; denn Data bekommt automatisch einen Move-Construktor, der wich für Rvalue-Quellen verantwortlich fühlt.