vault backup: 2022-09-30 21:53:03
Affected files: .obsidian/workspace 03. 專注Study/Android/ADB 取得 APK 的 icon.md 03. 專注Study/Android/ADB.md 03. 專注Study/Android/AOSP.md 03. 專注Study/Android/Android programming.md 03. 專注Study/Android/AudioTrack.md 03. 專注Study/Android/Ktor.md 03. 專注Study/Android/Service.md 03. 專注Study/Android/Tools.md 03. 專注Study/Android/UI.md 03. 專注Study/C++/C++17.md 03. 專注Study/C++/Class template.md 03. 專注Study/C++/GCC.md 03. 專注Study/C++/Structured binding declaration.md 03. 專注Study/C++/for_each.md 03. 專注Study/C++/lambda.md 03. 專注Study/C++/lvalue.md 03. 專注Study/C++/move operator.md 03. 專注Study/C++/rvalue.md 03. 專注Study/C++/智慧指標.md 03. 專注Study/RxKotlin/20200207 - Study RxKotlin.md 04. Programming/COM/20210726 - COM Interface.md 04. Programming/DB/MySQL.md 04. Programming/DB/sqlite.md 04. Programming/Design Pattern.md 04. Programming/FFMPEG/00. Introduction.md 04. Programming/FFMPEG/01. Setup.md 04. Programming/FFMPEG/FFMpeg.md 04. Programming/Flask.md 04. Programming/Kotlin/class.md 04. Programming/Kotlin/run, let, with, also 和 apply.md 04. Programming/Media Foundation/20210604 - Windows media foundation.md 04. Programming/OpenCV.md 04. Programming/OpenGL.md 04. Programming/Python/argparse.ArgumentParser.md 04. Programming/Python/decorator.md 04. Programming/Python/logging.md 04. Programming/Python/opencv.md 04. Programming/Python/subprocess.md 04. Programming/Python/threading.md 04. Programming/Python/tkinter.md 04. Programming/Python/檢測工具.md 04. Programming/QT/Dropdown button.md 04. Programming/QT/QVariant.md 04. Programming/QT/Qt.md 04. Programming/UML.md 04. Programming/演算法.md 05. 資料收集/99. templates/blogHeader.md 05. 資料收集/99. templates/date.md 05. 資料收集/99. templates/front matter.md 05. 資料收集/99. templates/note.md 05. 資料收集/99. templates/table.md 05. 資料收集/99. templates/thisWeek.md 05. 資料收集/99. templates/日記.md 05. 資料收集/99. templates/讀書筆記.md 05. 資料收集/Linux/CLI/cut.md 05. 資料收集/Linux/CLI/scp.md 05. 資料收集/Linux/CLI/timedatectl.md 05. 資料收集/Linux/Programming.md 05. 資料收集/Linux/Ubuntu.md 05. 資料收集/Tool Setup/Hardware/RaspberryPi.md 05. 資料收集/Tool Setup/Software/Chrome.md 05. 資料收集/Tool Setup/Software/Obisidian.md 05. 資料收集/Tool Setup/Software/SublimeText.md 05. 資料收集/Tool Setup/Software/VirtualBox.md 05. 資料收集/Tool Setup/Software/Visual Studio Code.md 05. 資料收集/Tool Setup/Software/Windows Setup.md 05. 資料收集/Tool Setup/Software/Windows Terminal.md 05. 資料收集/Tool Setup/Software/freefilesync.md 05. 資料收集/Tool Setup/Software/vim.md 05. 資料收集/名言佳句.md 05. 資料收集/架站/Gitea.md 05. 資料收集/架站/HTTP Server/Apache.md 05. 資料收集/架站/HTTP Server/Nginx/Reverse Proxy(Layer4).md 05. 資料收集/架站/Pelican blog.md 05. 資料收集/架站/Proxmox VE.md 05. 資料收集/架站/SWAG Reverse proxy.md 05. 資料收集/架站/Storj.md 05. 資料收集/架站/Trojan.md 05. 資料收集/每週外食.md 05. 資料收集/科技/802.11.md 05. 資料收集/科技/HDR Sensor.md 05. 資料收集/科技/量子電腦.md 05. 資料收集/科技/鋰電池.md 05. 資料收集/興趣嗜好/RC/Traxxas Sledge.md 05. 資料收集/興趣嗜好/RC/好盈電變調整中立點.md 05. 資料收集/興趣嗜好/RC/差速器調教教學.md 05. 資料收集/興趣嗜好/模型/舊化作例.md 05. 資料收集/興趣嗜好/軍武/虎式.md 05. 資料收集/讀書筆記/20201201 - 學習如何學習.md 05. 資料收集/讀書筆記/20201218 - Kotlin權威2.0.md 05. 資料收集/讀書筆記/20201224 - 寫作是最好的自我投資.md 05. 資料收集/讀書筆記/20210119 - 中產悲歌.md 05. 資料收集/讀書筆記/20210220 - 最高學習法.md 05. 資料收集/讀書筆記/20210320 - 最高學以致用法.md 05. 資料收集/讀書筆記/20210406 - 精準購買.md 05. 資料收集/讀書筆記/20210723 - 高手學習.md 05. 資料收集/讀書筆記/20220526 - 深入淺出設計模式.md 05. 資料收集/讀書筆記/20220619 - 精確的力量.md 05. 資料收集/軟體工具/IPFS.md 05. 資料收集/軟體工具/MkDocs.md 05. 資料收集/軟體工具/Obsidian.md 05. 資料收集/軟體工具/docker.md 05. 資料收集/軟體工具/git/apply.md 05. 資料收集/軟體工具/git/submodule.md 05. 資料收集/軟體工具/youtube-dl.md 05. 資料收集/面試準備/技术面试最后反问面试官的话.md
This commit is contained in:
210
03. 專注Study/C++/rvalue.md
Normal file
210
03. 專注Study/C++/rvalue.md
Normal 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)
|
||||
Reference in New Issue
Block a user