vault backup: 2025-07-22 22:14:08

Affected files:
Too many files to list
This commit is contained in:
2025-07-22 22:14:08 +08:00
parent 99f67788fc
commit 5d4e261181
202 changed files with 86 additions and 886 deletions

View File

@@ -0,0 +1,210 @@
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<uint8_t[]>(bufferSize);
}
~BigBuffer() {
std::cout << "BigBuffer destructor\n";
}
BigBuffer(const BigBuffer& src) {
std::cout << "BigBuffer copy constructor\n";
bufferSize = src.bufferSize;
buffer = std::make_unique<uint8_t[]>(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<uint8_t[]>(bufferSize);
std::memcpy(buffer.get(), src.buffer.get(), bufferSize);
return *this;
}
private:
int bufferSize = 0;
std::unique_ptr<uint8_t[]> 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)