之前稍微接觸過 c++ 11 move semantic, 那時候似懂非懂不是很清楚這是幹嘛的, 我一開始還把 move semantic 和 return value optimization 搞混了, return value optimization 並不需要 move semantic 才能做到。
再看過 rust 的 move semantic 之後, 才知道這東西的應用, rust 逼開發人員要搞懂這個, 不是壞事, 但我喜歡 c++, 讓我們從 c++ 來理解這個概念。
為了支援 move semantic, c++ 11 引入了 rvalue, 寫成 &&, ex: int &&ri, ri 就是一個 rvalue type。所以 ctor 又多了一個 rvalue 的版本 (ref L28); 當然 operator= 也是, 創造一個 class 要愈寫愈多程式碼。
rust 的這個概念是為了記憶體安全性, c++ 則是為了性能。簡單來說就是希望一個 object 不需要作 copy 的動作。什麼叫作「不需要作 copy 的動作」?
你可能需要自己寫一個類似 std::string (這是一個練習寫 class 的好方法) 的 class 才能理解 move semantic, 我剛好有一個現成的, 拿來理解這樣的行為正好, 順便支援 move semantic。DS::string 就是我的模仿品, 裡頭有個 char *str_, 複製一個 DS::string 需要連 str_ 一起複製, 若是用 move 則是把原本的 s1::str_ 轉到 s2::str_, 那你一定會問:「轉過去了, s1::str_ 剩下什麼? 什麼又是《轉過去》?」答案是你給他什麼就是什麼了。一般會給他 nullptr, 免得解構函式發動時出問題, 因為解構函式會執行 delete [] str_, 我是給 0。這就是為什麼 move 後原來的 s1::str_ 不能用, 不過 c++ compiler 可不像 rust 會幫你擋下來, 我在這個範例上還是照用, 結果就是預期的 null pointer。
複製為什麼比較慢, 看以下的程式碼行為應該就可以理解了:
比較奇怪的是 L118 若改成 void f3(DS::string &&s) 反而不會發動 move ctor, 這裡我不是很理解。
L141 std::move 就是來把這個 object 變成 rvalue, 用 f3((DS::string &&)(s1)); 也可以, 這樣才能發動 move ctor, L28 那個有兩個 && 就是 move ctor; 否則只會發動 copy ctor, 這就沒有節省執行時間了。
rust 預設行為是 move semantic, 我不知道這是不是好事情, 不過和 c++ move semantic 一樣, 都需要一點點的專業知識才能理解, 這是他們的高門檻。
最後想問, 大費周張搞了個 move semantic 可以用在哪些地方? 畢竟宣告了一個 object, 幾乎都會在繼續使用, 如果傳給一個 function 後就不能用了, 那寫起來會很不習慣, rust 就是這樣, 容器是一個很好的應用, 把 object 放進容器之後, 就可以用容器的 object, 並不在需要用這個 object 來操作, 所以放進容器的 object 就很適合 move semantic。另外有個網友補充了兩點, 感謝。
下面的參考連結寫的不錯, 不過我有自信你能看懂這篇的話, 應該不需要看以下兩個連結, 但是其提供的《
c++ copy and swap idiom 用法》讓我非常受用。
ref:
原本 L118 void f3(DS::string s) 時 L141 f3(std::move(s1)); 的 s1 是 move 給 f3 的 s,但改成 void f3(DS::string &&s) 的話 s1 就沒有 move 的對象啦。
回覆刪除另外 move 最重要的應用可能是這兩個:
1. 以前 type a = create_type(); 可以用 return value optimization 來少一次 ctor,但 type a; a = create_type() 就沒辦法。現在有 move 就可以直接 move 回來,可以少一個 ctor。
2. 假設寫一個 matrix class,重載 operator+ 的話,免不了會這樣寫 matrix operator+(const matrix& a, const matrix& b) { matrix tmp(a); tmp += b; return tmp; } 在過去為了消掉那個 tmp 通常會用 template expression 這種奇技淫巧。現在有了 move 就不必那樣做了,可以直接把 tmp move 給目的地。
感謝補充:
回覆刪除"原本 L118 void f3(DS::string s) 時 L141 f3(std::move(s1)); 的 s1 是 move 給 f3 的 s,但改成 void f3(DS::string &&s) 的話 s1 就沒有 move 的對象啦。"
這個在我反組譯之後有了新的理解, 類似傳 reference (pointer), 根本不會發動 (也用不著) 任何 ctor。
是呀,因為兩邊型別相同,所以單純就是 "forward" 這個變數過去而已。
回覆刪除一個很大的並通常也是主要的用途是在於臨時物件。
回覆刪除改寫程式 , 新增 five rules.
回覆刪除會發動 constructor , 是因為傳值要建立變數 , 如果是傳 ref 則不需要.
https://github.com/lanhsin/c-/blob/master/Ref.cpp
https://github.com/lanhsin/c-/blob/master/README.ref.md