Files
Obsidian-Main/02. PARA/03. Resources(資源)/C++17/rvalue.md
Awin Huang fa039f4641 vault backup: 2022-06-09 14:10:26
Affected files:
02. PARA/03. Resources(資源)/C++17/rvalue.md
2022-06-09 14:10:26 +08:00

3.5 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 assignment operator

假設我們有一個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
BigBuffer copy constructor, copy 104857600Bytes
BigBuffer destructor
BigBuffer destructor

這會先產生buf1然後把buf1 copy給buf2。如果我們想要省下copy的成本這時候 Move assignment operator就是 =)就可以派上用場了。 幫 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;
    }
    ...

這個 Move assignment operator 的參數就是一個 rvalue reference我們把來源的 bufferSize 跟 buffer指標「移到」我們這邊而不是完整的複製一份。在轉移之後呢當然也要把來源清空讓轉移更加明確。

有了 Move assignment operator 之後,在執行一次原本的程式,你會發現訊息......沒有變,還是一樣呼叫 copy assignment operator 來複製了100MB的buffer這時我們需要明確的告訴 compiler 我們要「移動」物件,而不是複製它,把原本的程式改為:

BigBuffer buf1;
// Do something with buf1
// Assign to buf2
BigBuffer buf2 = std::move(buf1);

我們用 std::move() 來「移動」物件,這時輸出變成


參考