rvalue Reference and Move Semantics: variable : name assigned to some Memory storage variable vs literal constant "variable has address, while literal constant not." "x = x + 1" lvalue : its location (read-write) prvalue: its value (read-only) lvalue glvalue expression xvalue rvalue prvalue glvalue: "named value" / value has "identity rvalue: something that can be moved lvalue: has identity & cannot be moved prvalue: something without identity & can be moved xvalue: something has identity & can be moved Temporary object & rvalue references: T* = (unnamed) int* a = new int; char* b = new char; ... accessed by pointer -> if lost pointer then we lost the object Temporary objects/values are another kind of unnamed objects/values created automatically on stack during: TO1: cosnt reference initialization TO2: argument passing (type conversion) TO3: function returned value (by copying) TO4: evaluation of expressions (result of sub-expressions) 1. Const reference initialization const int& c = 5; X::X(int n):data(n){} // conversion constructor X::X(const X& other){} // copy constructor const X& objref {10}; --> " alias of const X obj " = "int" First: temporary Object = X(int) Then : const X& obj = temporary object // Output: -- start of main -- X(int) X(const X&) --- end of main --- ~X() // The temporary object Have not been deleted right away // It Lives Longer! ->Const reference prolong a temporary object's Life period! 2. Argument passing (type conversion) void fun(X xobj){} int main(){ int b = 10; fun(b); // Here, a temporary object X(10) is created! } // Output: X(int) // Temporary Object X(const X&) // Pass by value & no copy elision fun(10) ~X() ~X() 3. function returned value (by copying) int square (int x){return x*x;} // (x*x) is a temporary object X fun_rbv(const X& obj){ cout << "fun_rbv(const X&)- "<<&obj< Temporary Object } X result = fun_rbv(obj); // Output: X(const X&) ~X() // Deletion of temporary Object! 4.evaluation of expressions (result of sub-expressions) result = (a+b) * (c+d) --> (a+b) and (c+d)'s result are temporary objects! X operator+(const X& a ,const X& b){ return (X(a.data + b.data)) // Create a temp object } X obj1(10),obj2(20); obj1 + obj2; output: X(int) // X(a.data + b.data) X(const X&) // Return Value ~X() ~X() 4.29 Content: 1. Double hashing 2. Temporary Object Examples 3. rvalue Reference Example 1: int square(int x) { return x * x; } void cbv(int x) { cout << "call-by-value: " << x << endl; } void cbr(int& x) { cout << "call-by-ref: " << x << endl; } void cbcr(const int& x) { cout << "call-by-const-ref: " << x << endl; } int main() { int a = 3; int& b = 4; // Error! cannot bind literal object to a non-const reference const int& c = 5; // TO1: const ref initialization int d = square(3); // TO3: function returned value int e = a + c + d; // TO4: result of sub-expression cbv(a); // OK: int x = a cbr(a); // OK: int& x = a cbr(8); // Error: int& x = 8 cbcr(8); // TO1: const int& x = 8 return 0; } class Word { private: int freq = 0; char* str = nullptr; public: Word() { cout << "default constructor" << endl; } Word(const char* s, int f = 1) : freq(f), str(new char [strlen(s)+1]) { strcpy(str, s); cout << "conversion: "; print(); } Word(const Word& w) : freq(w.freq+1), str(new char [strlen(w.str)+1]) { strcpy(str, w.str); cout << "copy: "; print(); } ~Word() { cout << "destructor: "; print(); delete [] str; } void print() const { cout << (str ? str : "null") << " ; " << freq << endl; } Word operator+(const Word& w) const { cout << "\n~~ " << str << " + " << w.str << " ~~" << endl; Word x; // Which constructor? → default constructor x.freq = freq + w.freq; x.str = new char [strlen(str) + strlen(w.str) + 1]; strcpy(x.str, str); strcat(x.str, w.str); return x; // How is x returned? → copy constructor (then destructor for x) } Word to_upper_case() const { cout << "\n-- to_upper_case(): " << str << " --" << endl; Word x(*this); // Which constructor? → copy constructor for (char* p = x.str; *p != '\0'; p++) *p += 'A' - 'a'; return x; // How is x returned? → copy constructor (then destructor for x) } }; void print_word(const Word& x) { cout << "<<<\n"; x.print(); cout << ">>>\n"; } int main() { const Word& w1 = "batman"; // TO1: const ref initialization // Output: conversion: batman ; 1 w1.print(); // Output: batman ; 1 print_word("superman"); // TO2: argument passing // Output: conversion: superman ; 1 superman ; 1 destructor: superman ; 1 Word w2 = w1.to_upper_case(); // TO3: function returned value // Output: copy: batman ; 2 copy: BATMAN ; 3 // Return by value -> temporary object destructor: BATMAN; 2 // End of function scope: delete x copy: BATMAN; 4 // Copy construct w2 destructor: BATMAN; 3 // deletion of temp obj w2.print(); // Output: BATMAN ; 4 ((w1 + " or ") + w2).print(); // TO4: result of sub-expression // Output: conversion: or ; 1 // initialization of temp object "or" --- batman + or --- default constructor // "Word x;" // freq = 1 + 1 = 2 copy: batman or ; 3 // Retrun By value -> temporary object destructor: batman or ; 2// End of scope, delete x --- batman or + w2 --- default constructor // "Word x;" // freq = 3 + 4 = 7 copy: batman or BATMAN ; 8 // Retrun By value -> temporary object destructor: batman or BATMAN; 7 // End of scope, delete x batman or BATMAN; 8 // ".print" destructor: batman or BATMAN; 8 desturctor: batman or ; 3 desturctor: or ; 1 // End of statement, delete all temporary object cout << "\n*** It's all destructions now ***" << endl; // Output: *** It's all destructions now *** desturctor: BATMAN ; 4 destructor: batman ; 1 return 0; } Remark: The "lifetime" of a temporary obj is at the end of "the expression that create it" Unless it is held by a const reference (prolong its lifetime) A temporary object held by a const reference dies as its reference variable goes out of scope, BUT if the reference is a function parameter, it will persist until the completion of the full expression containing the function call. rvalue Reference T&& = ; "an alias of a temporary object" int&& b; // Error! must be initialized, same as int& int&& c = a; // Error! cannot bind to a lvalue! int&& d = 5; // Okay TO1 cout<< d << endl; // Okay int&& e = square(5); // Okay, TO3 d = e = 10; // Okay Remarks: The lifetime of a temporary object is at the end of the expression that creates it unless it is held by an rvalue/const reference. (rvalue reference can also prolong its lifetime!) once bound, it cannot be re-bound to another temporary object Remarks: int&& a = 5; int&& b = a; // Error! "a" itself is treated as an lvalue! 5.4 Content: 1. Rvalue Reference Example Remark: int&& rref = 10; int & ref = rref; // Okay int&& another_rref = rref; // Error! rvalue reference itself is treated as a lvalue X b (X{}) // X{} create a temporary object // Copy construct X object // Output: default constructor X() copy constructor X(const X&) desturctor ~X() const X& xref = X(10); // const ref prolong temporary object's lifetime cout<< "after ref" << endl; // Output: default constructor after ref desturctor // Being desturct at the end of main! X&& xrref = X(10): // rvalue ref prolong temporary object's lifetime cout<< "after ref" << endl; // Output: default constructor after ref desturctor // Being desturct at the end of main! const X A{2}; X a{}; const X& cref = X{} ; // Okay, accept temporary object const X& cref2 = A; // OK , A -> lvalue -> bound to const ref X&& rref1 = a; // NO, a is a lvalue! X&& rref2 = X{}; // OK accept temporary object const X&& crref = X{10}; // OK const X&& crref1 = a; // NO const X&& crref2 = A; // NO rvalue reference can only bound to temporary object! no matter const or non-const! pass-by-rvalue reference void funX(X& obj) {cout <<"funX(X&)"< temporary object: X(100) // Passing by rvalue reference // Output: funX(X&&) Example 3: X&& xrref = X(99); funX(xrref) // xrref is treated as a "lvalue" // Output: funX(X&) Example 4: funX(X(10)); // TO: X(10) // Output: funX(X&&) Example 5: const X a; funX(a); // Output: funX(const X&) X& operator=(const X& other){} X&& rr= X{100}; rr.print(); rr = X{200}; // OK! x.data changed, but &x is not changed! (just like X&) rr.print(); if const reference: const X& r = X{100}; r = X{200}; // NO! since it is const! rvalue reference: We can change(modify/manage) the value of temporary object! void print_word(const Word& w) { cout << "print const Word&: "; w.print(); } void print_word(Word&& w) { cout << "print Word&&: "; w.print(); } int main() { /* Use const Word& to hold a temporary Word object */ Word song("imagine"); // conversion: imagine ; 1 const Word& w1 = song.to_upper_case(); // to_upper_case // copy: imagine ; 2 // copy: IMAGINE ; 3 // desturctor: imagine; 2 cout << endl; song.print(); // imagine ; 1 w1.print(); // IMAGINE ; 3 cout << "\n**********" << endl; /* Use Word&& to hold a temporary Word object */ Word movie("batman", 2); // conversion: batman; 2 Word&& w2 = movie.to_upper_case(); // copy: batman; 3 // copy: BATMAN; 4 // desturctor: BATMAN; 3 cout << endl; movie.print(); // batman; 2 w2.print(); // BATMAN; 4 cout << endl; print_word(song); // print const Word&: imagine ; 1 print_word(movie); //print const Word&: batman ; 2 print_word(w1); // print const Word&: IMAGINE ; 3 print_word(w2); //print const Word&: BATMAN ; 4 /* Directly pass a temporary Word object to a function */ print_word(movie.to_upper_case()); // copy: batman; 3 // copy: BATMAN; 4 // desturctor: BATMAN; 3 // print_word&& : BATMAN; 4 // desturctor; BATMAN;4 cout << endl; print_word("Beatles"); // conversion: Beatles ;1 // print_word&&: Beatles ;1 // desturctor: Beatles ; 1 cout << "\n**********" << endl; return 0; } Move constructor: move trick with rvalue references X obj { X{100} }; // TO : X{100} , will be destroyed soon if X has dynamic data member: X(100) // "new" allocation X(const X&) "new" allocation (deep copy) ~X() // "delete" Too many memory allocation/de-allocation! Why not: data = TO.data; // Why not shallow copy since TO will be destroyed TO.data = nullptr; // Set TO.data to nullptr to prevent double delete! "We're stealing TO's resources!" ==> Move constructor! X(X&& TO){ data = TO.data; TO.data = nullptr; } // Can only accept temporary object! // More efficient! Example: class Word { private: int freq = 0; char* str = nullptr; public: Word() { cout << "default constructor" << endl; } Word(const char* s, int f = 1) : freq(f), str(new char [strlen(s)+1]) { strcpy(str, s); cout << "conversion: "; print(); } Word(const Word& w) : freq(w.freq+1), str(new char [strlen(w.str)+1]) { strcpy(str, w.str); cout << "copy: "; print(); } // Move constructor Word(Word&& w) : freq(w.freq), str(w.str) { w.freq = 0; w.str = nullptr; cout << "move: "; print(); } ~Word() { cout << "destructor: "; print(); delete [] str; } Word to_upper_case() const { Word x(*this); for (char* p = x.str; *p != '\0'; p++) *p += 'A' - 'a'; return x; } void print() const { cout << (str ? str : "null") << " ; " << freq << endl; } // Copy assignment Word& operator=(const Word& w) { if (this != &w) { // No assignment for the same Word delete [] str; str = new char [strlen(w.str)+1]; freq = w.freq+1; strcpy(str, w.str); cout << "copy assignment: "; print(); } return *this; } // Move assignment Word& operator=(Word&& w) { if (this != &w) { // No assignment for the same Word delete [] str; freq = w.freq; str = w.str; w.freq = 0; w.str = nullptr; cout << "move assignment: "; print(); } return *this; } }; void print_word(const Word& w) { cout << "print const Word&: "; w.print(); } void print_word(Word&& w) { cout << "print Word&&: "; w.print(); } int main() { cout << "*** Copy Semantics ***" << endl; Word book {"batman"}; // conversion: batman ; 1 Word movie(book); // copy : batman ; 2 Word song("imagine")// conversion : imagine ; 1 movie = song; // copy assignment: print_word(book); cout << endl; cout << "*** Move Semantics ***" << endl; Word novel {"outliers"}; cout << endl; // conversion: outliers ; 1 Word novel2 = novel.to_upper_case(); // move construction // Output: copy : outliers ; 2 move : OUTLIERS ; 2 // Move construct! (Return x) destructor: null ; 0 move : OUTLIERS ; 2 // Move construct! (novel2 = TO) desturct: null ; 0 cout << endl; novel.print(); novel2.print(); cout << endl; Word band = "Beatles"; cout << endl; // move construction // conversion: Beatles ; 1 // move: Beatles ; 1 // desturctor: null ; 0 band = "Eagles"; cout << endl; // move assignment // conversion: Eagles; 1 // move assignment: Eagles; 1 // destructor: null ; 0 cout << "*** It’s all destructions now ***" << endl; return 0; } std::move --> static casting class Word { private data X; public: Word(Word&& other):data(other.data){} } int main(){ Word x = 10; // X(10) // X(const X&) // Here, X is not using move constructor! since other.data is not treated as rvalue ref // Word(Word&&) } ==> Using std::move(): Word(Word&& other):data(std::move(other.data)){} 5.6 content: 1. Some Examples of Move Semantics X fun(const X& obj){return obj;} X fun(X&& obj){return obj;} X fun(){ X x {100}; return x; } int main(){ X newObj = fun ( X{10}); // Output: X(int) fun(X&&) X(const X&) //return by value using copy costructor X(X&&) X newObj2 = fun(); // Output; fun() X(X&&) // if return a local variable-> using move constructor X(X&&) } class Word_Pair { private: Word w1; Word w2; public: // Pass by const&, construct by copying Word_Pair(const Word& a, const Word& b) : w1(a), w2(b) { cout << "\n-- Copy inputs --\n"; a.print(); b.print(); } // Pass by &, construct by moving Word_Pair(Word& a, Word& b) : w1(std::move(a)), w2(std::move(b)) { cout << "\n-- Move with inputs --\n"; a.print(); b.print(); } // Pass by rvalue reference &&, construct by moving Word_Pair(Word&& a, Word&& b) : w1(std::move(a)), w2(std::move(b)) { cout << "\n-- Another move with inputs --\n"; a.print(); b.print(); } void print() const { cout << "word1 = "; w1.print(); cout << "word2 = "; w2.print(); } }; #include "word-pair.h" int main() { cout << "\n*** Print the book’s info ***" << endl; Word author { "Stephen Hawking" }; Word title { "Brief History of Time" }; Word_Pair book { author, title }; book.print(); cout << "\n*** Print the book2’s info ***" << endl; Word_Pair book2 { book }; // Really memberwise copy book2.print(); cout << "\n*** Print the couple’s info ***" << endl; Word husband { "Mr. C++" }; Word wife { "Mrs. C++" }; Word_Pair couple { std::move(husband), std::move(wife) }; couple.print(); cout << "\n*** It’s all destructions now ***" << endl; return 0; } *** Print the book’s info *** conversion: Stephen Hawking; 1 conversion: Brief History of Time; 1 move: Stephen Hawking; 1 move: Brief History of Time; 1 -- Move with inputs -- null ; 0 null ; 0 word1 = Stephen Hawking; 1 word2 = Brief History of Time; 1 *** Print the book2’s info *** // copy constructor copy: Stephen Hawking; 2 copy: Brief History of Time; 2 // print word1 = Stephen Hawking; 2 word2 = Brief History of Time; 2 *** Print the couple’s info *** conversion: Mr. C++; 1 conversion: Mrs. C++; 1 move: Mr. C++; 1 move: Mrs. C++; 1 -- Another move with inputs -- null ; 0 null ; 0 word1 = Mr. C++; 1 word2 = Mrs. C++; 1 *** It’s all destructions now *** destructor: Mrs. C++; 1 desturctor: Mr. C++; 1 desturctor: null; 0 desturctor: null; 0 desturctor: Brief History of Time; 2 desturctor: Stephen Hawking; 2 destructor: Brief History of Time ; 1 destructor: Stephen Hawking ; 1 desturctor: null; 0 desturctor: null; 0