rvalue 是指: - 等號右邊的值 - 臨時的值,例如運算的結果 - 無法被取址(address-of)的物件 ## rvalue reference 一般的參考只能參考[[lvalue]],如下的程式是ok的: ```cpp int a = 10; int& b = a; ``` 但是像這樣就不行了: ```cpp int a = 10; int b = 5; int& c = a + b; ``` 因為`a+b`是一個 rvalue(臨時的值,沒辦法取址),所以無法參考。 但是可以用`&&`來參考 rvalue。例如: ```cpp int a = 10; int b = 5; int&& c = a + b; // c = 15 ``` 而不用這樣: ```cpp int a = 10; int b = 5; int r = a + b; int& c = r; ``` 了解 rvalue reference 之後,就可以實作類別的 move constructor 跟 move assignment operator。這可以減少複製的成本。 ## Move constructor 假設我們有一個 class 叫 BigBuffer,定義如下: ```cpp class BigBuffer { public: BigBuffer(int size=100*1024*1024) : bufferSize(size) { std::cout << "BigBuffer constructor\n"; this->buffer = std::make_unique(bufferSize); } ~BigBuffer() { std::cout << "BigBuffer destructor\n"; } BigBuffer(const BigBuffer& src) { std::cout << "BigBuffer copy constructor\n"; bufferSize = src.bufferSize; buffer = std::make_unique(bufferSize); std::memcpy(buffer.get(), src.buffer.get(), bufferSize); } BigBuffer& operator= (BigBuffer& src) { std::cout << "BigBuffer copy operator\n"; bufferSize = src.bufferSize; buffer = std::make_unique(bufferSize); std::memcpy(buffer.get(), src.buffer.get(), bufferSize); return *this; } private: int bufferSize = 0; std::unique_ptr buffer = nullptr; }; ``` 這個 class 的特色就是每一次使用都會佔用100MB的記憶體空間,想像下面的程式的動作: ```cpp BigBuffer buf1; // Do something with buf1 // Assign to buf2 BigBuffer buf2 = buf1; ``` 執行訊息: ``` BigBuffer constructor // create buf1 BigBuffer copy constructor, copy 104857600Bytes // copy buf1 to buf2 ... ``` 這會先產生 buf1,然後把 buf1 copy 給 buf2。如果我們想要省下 copy 的成本,這時候 Move constructor 就可以派上用場了。 幫 BigBuffer 加一個 Move constructor: ```cpp class BigBuffer { public: ... BigBuffer(BigBuffer&& src) noexcept { std::cout << "BigBuffer move constructor\n"; bufferSize = src.bufferSize; buffer = std::move(src.buffer); src.buffer.reset(); src.bufferSize = 0; } ... }; ``` 這個 move constructor 的參數就是一個 rvalue reference,我們把來源的 bufferSize 跟 buffer 指標「移到」我們這邊,而不是完整的複製一份。在轉移之後呢,當然也要把來源清空,讓轉移更加明確。 有了 Move assignment operator 之後,在執行一次原本的程式,你會發現訊息......沒有變,還是一樣呼叫 copy constructor 來複製了100MB 的 buffer,這時我們需要明確的告訴 compiler 我們要「移動」物件,而不是複製它,把原本的程式改為: ```cpp BigBuffer buf1; // Do something with buf1 // Assign to buf2 BigBuffer buf2 = std::move(buf1); ``` 我們用 `std::move()` 來「移動」物件,這時輸出變成 ``` BigBuffer constructor // create buf1 BigBuffer move constructor // move buf1 to buf2, buf1 has nullptr now ... ``` 另外一個情形也可以受益於此,假如我們有個 function 會產生 `BigBuffer`,如下: ```cpp BigBuffer BigBufferCreator() { std::cout << "BigBufferCreator: Create a BigBuffer!\n"; BigBuffer tempb; // do something std::cout << "BigBufferCreator: return\n"; return tempb; } BigBuffer b = BigBufferCreator(); // copy tempb to b ``` 在沒有 Move constructor 的情況下,上面的程式會先產生一個 `tempb`,然後複製給 `b`,訊息: ``` BigBufferCreator: Create a BigBuffer! BigBuffer constructor BigBufferCreator: return BigBuffer copy constructor, copy 104857600Bytes // Copy 100MB! ... ``` 在有 Move constructor 的情況下,訊息就變成: ``` BigBufferCreator: Create a BigBuffer! BigBuffer constructor BigBufferCreator: return BigBuffer move constructor // Use MOVE! BigBuffer destructor BigBuffer destructor ``` 因為 `BigBufferCreator()` 產生的就是一個 `BigBuffer` rvalue,所以 compiler 會使用 move constructor(`BigBuffer(BigBuffer&& src)`) 而不是 copy constructor。 ## Move assignment operator(`=`) Move assignment operator 的行為跟 move constructor 是一樣的,幫 `BigBuffer` 加入 move assignment operator: ```cpp class BigBuffer { public: ... BigBuffer& operator=(BigBuffer&& src) noexcept { std::cout << "BigBuffer move operator\n"; bufferSize = src.bufferSize; buffer = std::move(src.buffer); src.buffer.reset(); src.bufferSize = 0; return *this; } ... }; ``` 測試程式: ```cpp BigBuffer b1, b2; b2 = b1; ``` 訊息: ``` BigBuffer constructor BigBuffer constructor BigBuffer copy operator, copy 104857600Bytes ``` 還是使用 copy assignment operator 來複製,理由是一樣的,需要一個明確的 `std::move()` 來表示「轉移」的行動,把程式改成: ```cpp BigBuffer b1, b2; b2 = std::move(b1); ``` 這樣就可以了。訊息: ``` BigBuffer constructor BigBuffer constructor BigBuffer move operator // Use MOVE! ``` ## 參考 - [Value categories - cppreference.com](https://en.cppreference.com/w/cpp/language/value_category) - [rvalue 參考](https://openhome.cc/Gossip/CppGossip/RvalueReference.html) - [Move constructors - cppreference.com](https://en.cppreference.com/w/cpp/language/move_constructor) - [Move assignment operator - cppreference.com](https://en.cppreference.com/w/cpp/language/move_assignment)