Files
Obsidian-Main/01. 個人/02. 專注Study/C++/rvalue.md
Awin Huang 0a8e11189d vault backup: 2022-09-26 18:49:43
Affected files:
.obsidian/daily-notes.json
.obsidian/plugins/periodic-notes/data.json
.obsidian/templates.json
.obsidian/workspace
01. 個人/02. 專注Study/Android/ADB 取得 APK 的 icon.md
01. 個人/02. 專注Study/Android/ADB.md
01. 個人/02. 專注Study/Android/AOSP.md
01. 個人/02. 專注Study/Android/Android programming.md
01. 個人/02. 專注Study/Android/Ktor.md
01. 個人/02. 專注Study/Android/Service.md
01. 個人/02. 專注Study/Android/Tools.md
01. 個人/02. 專注Study/Android/UI.md
01. 個人/02. 專注Study/C++/C++17.md
01. 個人/02. 專注Study/C++/Class template.md
01. 個人/02. 專注Study/C++/Structured binding declaration.md
01. 個人/02. 專注Study/C++/for_each.md
01. 個人/02. 專注Study/C++/lambda.md
01. 個人/02. 專注Study/C++/lvalue.md
01. 個人/02. 專注Study/C++/move operator.md
01. 個人/02. 專注Study/C++/rvalue.md
01. 個人/02. 專注Study/C++/智慧指標.md
01. 個人/02. 專注Study/RxKotlin/20200207 - Study RxKotlin.md
03. 資料收集/01. Programming/COM/20210726 - COM Interface.md
03. 資料收集/01. Programming/DB/MySQL.md
03. 資料收集/01. Programming/DB/sqlite.md
03. 資料收集/01. Programming/Design Pattern.md
03. 資料收集/01. Programming/FFMPEG/00. Introduction.md
03. 資料收集/01. Programming/FFMPEG/01. Setup.md
03. 資料收集/01. Programming/FFMPEG/FFMpeg.md
03. 資料收集/01. Programming/Flask.md
03. 資料收集/01. Programming/Media Foundation/20210604 - Windows media foundation.md
03. 資料收集/01. Programming/OpenCV.md
03. 資料收集/01. Programming/OpenGL.md
03. 資料收集/01. Programming/Python/argparse.ArgumentParser.md
03. 資料收集/01. Programming/Python/decorator.md
03. 資料收集/01. Programming/Python/logging.md
03. 資料收集/01. Programming/Python/opencv.md
03. 資料收集/01. Programming/Python/subprocess.md
03. 資料收集/01. Programming/Python/threading.md
03. 資料收集/01. Programming/Python/tkinter.md
03. 資料收集/01. Programming/Python/檢測工具.md
03. 資料收集/01. Programming/QT/Dropdown button.md
03. 資料收集/01. Programming/QT/QVariant.md
03. 資料收集/01. Programming/QT/Qt.md
03. 資料收集/01. Programming/UML.md
03. 資料收集/01. Programming/演算法.md
03. 資料收集/99. templates/blogHeader.md
03. 資料收集/99. templates/date.md
03. 資料收集/99. templates/front matter.md
03. 資料收集/99. templates/note.md
03. 資料收集/99. templates/table.md
03. 資料收集/99. templates/thisWeek.md
03. 資料收集/99. templates/日記.md
03. 資料收集/99. templates/讀書筆記.md
03. 資料收集/Hobby/RC.md
03. 資料收集/Hobby/RC/Traxxas Sledge.md
03. 資料收集/Hobby/RC/好盈電變調整中立點.md
03. 資料收集/Hobby/RC/差速器調教教學.md
03. 資料收集/Linux/CLI/cut.md
03. 資料收集/Linux/CLI/scp.md
03. 資料收集/Linux/CLI/timedatectl.md
03. 資料收集/Programming/Qt.md
03. 資料收集/Tool Setup/Hardware/RaspberryPi.md
03. 資料收集/Tool Setup/Software/Chrome.md
03. 資料收集/Tool Setup/Software/Obisidian.md
03. 資料收集/Tool Setup/Software/SublimeText.md
03. 資料收集/Tool Setup/Software/VirtualBox.md
03. 資料收集/Tool Setup/Software/Visual Studio Code.md
03. 資料收集/Tool Setup/Software/Windows Setup.md
03. 資料收集/Tool Setup/Software/Windows Terminal.md
03. 資料收集/Tool Setup/Software/freefilesync.md
03. 資料收集/Tool Setup/Software/vim.md
03. 資料收集/翻牆/V2Ray.md
03. 資料收集/翻牆/Wireguard.md
03. 資料收集/軟體工具/youtube-dl.md
2022-09-26 18:49:43 +08:00

5.7 KiB
Raw Blame History

rvalue 是指:

  • 等號右邊的值
  • 臨時的值,例如運算的結果
  • 無法被取址address-of的物件

rvalue reference

一般的參考只能參考lvalue如下的程式是ok的

int a = 10;
int& b = a;

但是像這樣就不行了:

int a = 10;
int b = 5;
int& c = a + b;

因為a+b是一個 rvalue臨時的值沒辦法取址所以無法參考。
但是可以用&&來參考 rvalue。例如

int a = 10;
int b = 5;
int&& c = a + b; // c = 15

而不用這樣:

int a = 10;
int b = 5;
int r = a + b;
int& c = r;

了解 rvalue reference 之後,就可以實作類別的 move constructor 跟 move assignment operator。這可以減少複製的成本。

Move constructor

假設我們有一個 class 叫 BigBuffer定義如下

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的記憶體空間想像下面的程式的動作

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

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 我們要「移動」物件,而不是複製它,把原本的程式改為:

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,如下:

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 constructorBigBuffer(BigBuffer&& src) 而不是 copy constructor。

Move assignment operator(=)

Move assignment operator 的行為跟 move constructor 是一樣的,幫 BigBuffer 加入 move assignment operator

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;
    }
    ...
};

測試程式:

BigBuffer b1, b2;
b2 = b1;

訊息:

BigBuffer constructor
BigBuffer constructor
BigBuffer copy operator, copy 104857600Bytes

還是使用 copy assignment operator 來複製,理由是一樣的,需要一個明確的 std::move() 來表示「轉移」的行動,把程式改成:

BigBuffer b1, b2;
b2 = std::move(b1);

這樣就可以了。訊息:

BigBuffer constructor
BigBuffer constructor
BigBuffer move operator // Use MOVE!

參考