so true

          心懷未來,開創(chuàng)未來!
          隨筆 - 160, 文章 - 0, 評論 - 40, 引用 - 0
          數(shù)據(jù)加載中……

          深度剖析右值、右值引用、完美轉(zhuǎn)發(fā)等相關(guān)概念

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

          主站蜘蛛池模板: 奉新县| 新昌县| 昆山市| 金堂县| 拜城县| 宜黄县| 天峻县| 綦江县| 怀柔区| 咸丰县| 观塘区| 湖州市| 渑池县| 平昌县| 道孚县| 肃北| 通州区| 水城县| 长寿区| 乐山市| 宣汉县| 湖口县| 东乡| 巴东县| 五寨县| 博湖县| 上林县| 泰顺县| 望谟县| 大同市| 元阳县| 神池县| 潜江市| 贵州省| 东乌| 柳河县| 古丈县| 临夏市| 曲沃县| 抚松县| 邢台县|