看板 C_and_CPP 關於我們 聯絡資訊
個人學習速度慢, 2016 了才學到 2011 的東西。 精華區有個很仔細的版本, 我這篇不那麼長。 之前稍微接觸過 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 reference, 寫成 &&, ex: int &&ri, ri 就是一個 rvalue reference 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。 複製為什麼比較慢, 看以下的程式碼行為應該就可以理解了: 複製: strcpy(s2::str_, s1:str_) move : s2::str_ = s1:str_ 比較奇怪的是 L118 若改成 void f3(DS::string &&s) 反而不會發動 move ctor, 這裡 我不是很理解。 ANS 這個在我反組譯之後有了新的理解, 類似傳 reference (pointer), 根本不會發動 (也用 不著) 任何 ctor。 L141 std::move 就是來把這個 object 變成 rvalue, 用 f3((DS::string &&)(s1)); 也 可以, 這樣才能發動 move ctor, L28 那個有兩個 && 就是 move ctor; 否則只會發動 copy ctor, 這就沒有節省執行時間了。 mystring.cpp 1 #include "mystring.h" 2 #include "myiostream.h" 3 #include "mem.h" 4 5 #ifdef TEST 6 #include <cstdio> 7 using namespace std; 8 #else 9 // #define std DS 10 #endif 11 12 DS::string::string():len_(0), str_(0) 13 { 14 #ifdef TEST 15 std::printf("1 ctor\n"); 16 #endif 17 } 18 19 DS::string::string(const char *str) 20 { 21 generate_string(str, s_strlen(str)); 22 23 #ifdef TEST 24 std::printf("const char *str ctor\n"); 25 #endif 26 } 27 28 DS::string::string(string &&s) 29 { 30 str_ = s.str_; 31 len_ = s.len_; 32 s.str_ = 0; 33 s.len_ = 0; 34 #ifdef TEST 35 std::printf("move ctor\n"); 36 #endif 37 } 38 39 DS::string::string(const string &s) 40 { 41 generate_string(s.c_str(), s.length()); 42 43 #ifdef TEST 44 std::printf("copy ctor\n"); 45 #endif 46 } 47 48 DS::string::~string() 49 { 50 #ifdef TEST 51 std::printf("11 dtor:%s\n", str_); 52 #endif 53 delete [] str_; 54 //cout << "string ~ctor" << endl; 55 } 56 113 114 #ifdef TEST 115 #include <stdio.h> 116 #include <utility> 117 118 void f3(DS::string s) 119 { 120 printf("f3 s: %s\n", s.c_str()); 121 } 122 123 DS::string f2() 124 { 125 DS::string s1{"f2"}; 126 return s1; 127 } 128 129 DS::string f1() 130 { 131 std::printf("bb\n"); 132 DS::string s1{"return str"}; 133 std::printf("ee\n"); 134 return s1; 135 } 136 137 int main(int argc, char *argv[]) 138 { 139 DS::string s1=f2(); 140 printf("s1: %s\n", s1.c_str()); 141 f3(std::move(s1)); 142 printf("s1: %s\n", s1.c_str()); 143 printf("s1.length(): %d\n", s1.length()); 144 183 return 0; 184 } 185 #endif list 1. 執行結果 1 const char *str ctor 2 s1: f2 3 move ctor 4 f3 s: f2 5 11 dtor:f2 6 s1: (null) 7 s1.length(): 0 8 11 dtor:(null) rust 預設行為是 move semantic, 我不知道這是不是好事情, 不過和 c++ move semantic 一樣, 都需要一點點的專業知識才能理解, 這是他們的高門檻。 最後想問, 大費周張搞了個 move semantic 可以用在哪些地方? 畢竟宣告了一個 object, 幾乎都會在繼續使用, 如果傳給一個 function 後就不能用了, 那寫起來會很不 習慣, rust 就是這樣, 容器是一個很好的應用, 把 object 放進容器之後, 就可以用容 器的 object, 並不在需要用這個 object 來操作, 所以放進容器的 object 就很適合 move semantic。另外有個網友補充了兩點, 感謝。 下面的參考連結寫的不錯, 不過我有自信你能看懂這篇的話, 應該不需要看以下兩個連結 , 但是其提供的《c++ copy and swap idiom 用法 ( https://goo.gl/NU7HmE )》讓我非 常受用。 ref: 翻:怎理解 C++ 11中的move(基)--- An answer from stackoverflow ( http://goo.gl/jElp2n ) 翻:怎理解 C++ 11中的move(深入)--- An answer from stackoverflow ( http://goo.gl/wdV2Ot ) // 本文使用 Blog2BBS 自動將Blog文章轉成縮址的BBS純文字 http://goo.gl/TZ4E17 // blog 版本 http://descent-incoming.blogspot.tw/2016/05/c-11-move-semantic.html -- 紙上得來終覺淺,絕知此事要躬行。 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 1.200.59.8 ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1465141530.A.911.html ※ 編輯: descent (1.200.59.8), 06/05/2016 23:46:17
chiwa: rvalue 不等於 rvalue reference 06/06 20:10
感謝提醒
Dannvix: 延伸閱讀Scott Meyers on Uni. Ref. http://j.mp/1su76iV 06/06 20:17
※ 編輯: descent (223.142.207.206), 06/09/2016 16:39:11