so true

          心懷未來,開創未來!
          隨筆 - 160, 文章 - 0, 評論 - 40, 引用 - 0
          數據加載中……

          深度剖析右值、右值引用、完美轉發等相關概念

          右值、右值引用(T&&)、std::move、完美轉發(std::forward)、通用引用(universal reference)、引用折疊(reference collapsing)這些概念貌似很玄,但其實很它們想做的事情很簡單而且很單一,抓住本質就豁然開朗了。
          上面這些概念的引入完全是為了解決一個問題:盡可能用淺拷貝代替深拷貝;具體該如何實施呢:
          1. 定義move constructor或move operator=,如果你新定義的類沒有move constructor或move operator=,那根本就用不上上面那些高級的概念;
          2. 告訴編譯器什么時候用move constructor(而不是用copy constructor),這就需要借助std::move來把左值轉成右值;
          下面是一些零散的箴言:
          左值:能用&來取地址的東西(有名字的都是左值,不管其是否是const的,而且引用都是左值,因為引用肯定得起個名字去引用另外一個東西);
          右值:不能用&來取地址的東西(說白了就是沒名字的東西,例如函數的返回值);
          const T&是萬能引用,即 const T& t = XXX; //XXX是左值、右值都可以;
          T& t = XXX; //XXX必須是左值;
          T&& t = XXX; //XXX必須是右值;
          const T&& t = XXX; //XXX必須是右值;
          不要把T&&和const T&劃等號,兩個都能hold住臨時變量,給臨時變量續命,但:
          T&& t1 = get_val();
          const T& t2 = get_val();
          t1和t2都是左值,因為都可以用&來取地址,但t1是非const的,因此可以t1.xxx = yyy;去修改t1里的內容(當然如果你定義const T&& t1 = get_val(),那就不能更改了),這樣一來,下面這種代碼受益了:
              string name = get_name(); //因為后面要修改name,所以不能用const string& name
              trim(name);
              可以修改為:
              string&& name = get_name();
              trim(name);
              
          關于右值引用的生命周期(續命能續多久,下面討論的也適用于const T&):
          1. 函數不能返回對局部變量的引用(右值引用也不行),這是鐵的原則;
          2. 引用一旦建立(用一個名字hold住了臨時變量),例如T&& t = get_val();那么這個臨時變量的析構時機就是變量t退出其作用域的時候,因此下面的代碼是錯誤的:
              struct A {
                  A(T&& t): _t(t) {};
                  T&& _t;
              };
              A* pa = NULL;
              {
                  T&& t = get_val();
                  pa = new A(t);
              }
              pa->_t; //runtime crash, _t已經析構了
              
          std::move只做一件事:把左值轉成右值,因此T t(std::move(get_val()));不管什么情況下都能保證調用move constructor來構造t;
          c++0x里stl的各種容器都已經新增了move constructor,因此當你希望使用淺拷貝的時候可以借助std::move來達成所愿了,至于什么時候可以用淺拷貝,分兩種情況,有些情況編譯器會默認幫你去調用move constructor,但在你自己新定義的函數或者類成員方法里就需要你自己來判斷了,大的原則就是:這個變量只是傳遞過去就好,中間可以轉好幾道手,但在傳遞過程中不需要做修改;
          完美轉發(std::forward),談到這個就必須得談到通用引用(universal reference)、引用折疊(reference collapsing),其實這幾個概念是捆綁在一起的,而且只用在模板范疇內,而且只是為了解決下面這一種模式:
          template <typename T>
          void func2(T t) {
          }
          template <typename T>
          void func(T&& t) {
              func2(std::forward<T>(t));
          }
          上面這個到底完美在哪里?
          func(get_val()); //這個會導致最后是通過move constructor來構造func2函數里的參數t
          T t0 = get_val();
          T& t1 = t0;
          const T& t2 = get_val();
          T&& t3 = get_val();
          const T&& t4 = get_val();
          func(t0); func(t1); func(t2); func(t3); func(t4); //這5個都會導致最后是通過copy constructor來構造func2函數里的參數t,因為這個5個t*都是左值(有名字的就是左值);
              插播兩個你有可能費解的地方:
              1. func函數的參數是T&&, t0、t1都是左值,不是說不能用左值賦值給右值嗎?這就要提到引用折疊了(詳情可參見最后我推薦的文章),說白了因為func是個模板函數才能這么干;
              2. t3類型是T&&,func的參數也是T&&,把t3傳遞過去,居然還是調用copy contructor,因為t3是右值引用,它引用了一個右值,但它本身卻是左值,到了func函數內部,func函數只能知道它是個左值,了解不到它原本的面貌居然是個右值引用;如果還不理解,再看下下面:
              template <typename T>
              void print(T t) {
              }
              int x1 = 1; int& x2 = x1; const int& x3 = x1; int* x4 = &x1;
              分別用x{1..4}來調用print方法,你肯定知道x1、x2、x3到了print函數里,T就是int,只有x4會被print函數認為T是int*,引用這個概念在模板類型推導時是無法讓模板參數感知到的;此外print(T)和print(T&)是不能同定義的,編譯器會抱怨ambiguous,這個事實也能幫你多一些理解。
          如果想讓上面的這5個最終能通過move constructor來構造func2函數里的參數t,那么只要給每個都用func(std::move(t*))包裝下就可以了;
          再澄清下,func2函數的參數是類型T,并不是引用,因此無論如何都需要生成T的一個新實例,區別就是到底是通過copy constructor還是move constructor來生成了;
          所以,所謂的完美就是體現在了forward能把本來是左值的按左值來傳遞,本來是右值的按照右值來傳遞,具體來說就是作為中間環節的func函數內部實現過程中使用了func2(std::forward<T>(t));這句話,使得可以按照調用方實際的真實情況告知給func2函數如何構造參數t,這有什么好處呢,還是那句話:在適當的時機做合適的引導,讓編譯器幫你調用淺拷貝。
          除此之外,你完全可以忘掉這些玄乎其神的概念,所以只要會套用就可以了。
          以上是一些梗概性或結論性的東西,再結合下面這篇文章,把骨頭之外的血肉補上吧:
          https://codinfox.github.io/dev/2014/06/03/move-semantic-perfect-forward/

          posted on 2018-12-06 11:38 so true 閱讀(531) 評論(0)  編輯  收藏 所屬分類: C&C++

          主站蜘蛛池模板: 和田市| 南阳市| 剑河县| 乌拉特后旗| 台江县| 栾川县| 横峰县| 荃湾区| 长丰县| 宜城市| 曲水县| 喜德县| 潞城市| 辽宁省| 皮山县| 德钦县| 蓝山县| 苏尼特右旗| 封开县| 郓城县| 辽宁省| 奉节县| 绥阳县| 南康市| 油尖旺区| 焦作市| 新昌县| 申扎县| 保德县| 都匀市| 鹤岗市| 枝江市| 龙陵县| 石楼县| 汉川市| 兴安县| 邻水| 仁布县| 镇沅| 青河县| 伊金霍洛旗|