• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            流量統(tǒng)計(jì):
            Rixu Blog (日需博客)
            日需博客,每日必需來(lái)踩踩哦..
            posts - 108,comments - 54,trackbacks - 0

            C++一直致力于生成快速的程序。不幸的是,直到C++11之前,這里一直有一個(gè)降低C++程序速度的頑癥:臨時(shí)變量的創(chuàng)建。有時(shí)這些臨時(shí)變量可以被編譯器優(yōu)化(例如返回值優(yōu)化),但是這并不總是可行的,通常這會(huì)導(dǎo)致高昂的對(duì)象復(fù)制成本。我說(shuō)的是怎么回事呢?

            讓我們一起來(lái)看看下面的代碼:

            復(fù)制代碼
            復(fù)制代碼
             1 #include <iostream> 2 #include <vector> 3 using namespace std; 4 5 vector<int> doubleValues (const vector<int>& v) 6 { 7 vector<int> new_values( v.size() ); 8 for (auto itr = new_values.begin(), end_itr = new_values.end(); itr != end_itr; ++itr ) 9 { 10 new_values.push_back( 2 * *itr ); 11 } 12 return new_values; 13 } 14 15 int main() 16 { 17 vector<int> v; 18 for ( int i = 0; i < 2; i++ ) 19 { 20 v.push_back( i ); 21 } 22 v = doubleValues( v ); 23 }
            復(fù)制代碼
            復(fù)制代碼

            (筆者注:代碼中的vector<int> doubleValues (const vector<int>& v)函數(shù)是對(duì)vector v中的值乘以2,存儲(chǔ)到另外一個(gè)vector中并返回。如果我們?cè)诘?2行添加如下代碼輸出v中的值,會(huì)發(fā)現(xiàn)v中的值并沒(méi)有改變,都是0。

            for (auto x : v) cout << x << endl;

            應(yīng)該改成這樣:

            復(fù)制代碼
            復(fù)制代碼
             1 #include <iostream> 2 #include <vector> 3 using namespace std; 4 5 vector<int> doubleValues (const vector<int>& v) 6 { 7 vector<int> new_values; 8 for (auto x : v) 9 new_values.push_back(2 * x); 10 return new_values; 11 } 12 13 int main() 14 { 15 vector<int> v; 16 for ( int i = 0; i < 2; i++ ) 17 { 18 v.push_back( i ); 19 } 20 v = doubleValues( v ); 21 }
            復(fù)制代碼
            復(fù)制代碼

            另外,筆者不建議像原作者這樣使用vector,因?yàn)閜ush_back會(huì)改變?cè)瓉?lái)vector的內(nèi)存分布和大小,會(huì)出現(xiàn)一些無(wú)法預(yù)料的錯(cuò)誤,代碼也不健壯。)

              如果你已經(jīng)做了大量高性能優(yōu)化工作,很抱歉這個(gè)頑癥給你帶來(lái)的痛苦。如果你并未做此類(lèi)優(yōu)化工作,那好,讓我們一起來(lái)縷縷為什么這樣的代碼在C++03是噩夢(mèng)(接下來(lái)的部分是說(shuō)明為什么C++11在此方面更好)。該問(wèn)題與復(fù)制變量有關(guān),當(dāng)doubleValues()函數(shù)被調(diào)用時(shí),它會(huì)構(gòu)造一個(gè)臨時(shí)的vector(即new_values),并填充數(shù)據(jù)。單獨(dú)這樣做效率并不高,但是若想保持原始vector的純凈,我們就需要另外一份拷貝。想想doubleValues()函數(shù)返回發(fā)生了什么?

              new_values中的所有數(shù)據(jù)必須被重新復(fù)制一遍!理論上,這里可能最多有2次的復(fù)制操作:

              1、發(fā)生在返回的臨時(shí)變量;

              2、發(fā)生在v = doubleValues( v );這里。

            第一次的復(fù)制操作可能會(huì)被編譯器自動(dòng)優(yōu)化掉(即返回值優(yōu)化),但是第二次在給將臨時(shí)變量復(fù)制給v時(shí)是無(wú)法避免的,因?yàn)檫@里需要重新分配內(nèi)存空間,并且需要迭代整個(gè)vector。

              這里的例子可能有些小題大做。當(dāng)然,你可以通過(guò)其他方法避免這種問(wèn)題,比如通過(guò)指針或者傳遞一個(gè)已經(jīng)填充的vector。事實(shí)這兩種編程方法都是合情合理的。此外返回一個(gè)指針的方法至少需要一次的內(nèi)存分配,避免內(nèi)存分配也是C++設(shè)計(jì)目標(biāo)之一。

              最糟糕的是,在整個(gè)過(guò)程中函數(shù)doubleValues()返回的值是一個(gè)不再需要的臨時(shí)變量。當(dāng)執(zhí)行到v = doubleValues( v )這里時(shí),復(fù)制操作一旦完成,v = doubleValues( v )的結(jié)果就將被丟棄。理論上是可以避免整個(gè)復(fù)制過(guò)程,僅僅將臨時(shí)vector的指針保存到v中。實(shí)際上,我們?yōu)槭裁床灰苿?dòng)對(duì)象呢?在C++03中,無(wú)論對(duì)象是否為臨時(shí)的,我們都不得不在復(fù)制操作符=或復(fù)制構(gòu)造函數(shù)中運(yùn)行相同的代碼,不管該值來(lái)之哪里,所以這里”偷竊(pilfering)”是不可能的。在C++11這種行為是可以的!

              這就是右值和move語(yǔ)義!當(dāng)你在使用會(huì)被丟棄的臨時(shí)變量時(shí),move語(yǔ)義能為你避免不必要的復(fù)制拷貝,并且這些來(lái)自臨時(shí)變量的資源能夠被用于其他地方。move語(yǔ)義是C++11新的特性,被稱(chēng)為右值引用,你也想明白這能為程序員們帶來(lái)怎樣的好處。首先我們先來(lái)說(shuō)說(shuō)什么是右值,然后說(shuō)說(shuō)什么是右值引用,最后我們將回到move語(yǔ)義,并看看右值引用是如何實(shí)現(xiàn)的。

            右值和左值-勁敵還是好友?

              在C++中有左值和右值之分。左值就是一個(gè)可以獲取地址的表達(dá)式,即一個(gè)內(nèi)存地址定位器地址-本質(zhì)上,一個(gè)左值能夠提供一個(gè)半永久的內(nèi)存。我們可以給左值賦值,例如:

            1 int a; 2 a = 1; // here, a is an lvalue

            也可以使左值不是變量,如:

            復(fù)制代碼
            復(fù)制代碼
            1 int x; 2 int& getRef () 3 { 4 return x; 5 } 6 7 getRef() = 4;
            復(fù)制代碼
            復(fù)制代碼

              這里getRef()返回一個(gè)全局變量的引用,所以它的返回值是被存儲(chǔ)在內(nèi)存中的永久位置處。你可以像使用普通的變量一樣來(lái)使用getRef()。

              如果一個(gè)表達(dá)式返回一個(gè)臨時(shí)變量,則該表達(dá)式是右值。例如:

            復(fù)制代碼
            1 int x; 2 int getVal () 3 { 4 return x; 5 } 6 getVal();
            復(fù)制代碼

              這里getVal()是右值,因?yàn)榉祷刂祒不是全局變量x的引用,僅僅是一個(gè)臨時(shí)變量。如果我們用對(duì)象而不是數(shù)字,這將有點(diǎn)意思,如:

            1 string getName () 2 { 3 return "Alex"; 4 } 5 getName();

              getName()返回一個(gè)在函數(shù)內(nèi)部構(gòu)造的string對(duì)象,你可以將其賦值給變量:

            string name = getName();

            此時(shí)你正在使用臨時(shí)變量,getName()是右值。

            檢測(cè)右值引用的臨時(shí)對(duì)象

              右值涉及到臨時(shí)對(duì)象-就像doubleValues()返回值。如果我們非常清楚的知道從一個(gè)表達(dá)式返回的值是臨時(shí)的,并知道如何編寫(xiě)重載臨時(shí)對(duì)象的方法,這不是很好么?為什么,事實(shí)的確如此。什么是右值引用,就是綁定到臨時(shí)對(duì)象的引用!
              在C++11之前,如果有一個(gè)臨時(shí)對(duì)象,就需要使用“正式(regular)”或者“左值引用(lvalue reference)”來(lái)綁定,但如果該值是const呢?如:
            1 const string& name = getName(); // ok 2 string& name = getName(); // NOT ok

            顯而易見(jiàn)這里不能使用一個(gè)“可變(mutable)”引用,因?yàn)槿绻@么做了,你將可以修改即將銷(xiāo)毀的對(duì)象,這是相當(dāng)危險(xiǎn)的。順便提醒一下,將臨時(shí)對(duì)象保存在const引用中可以確保該臨時(shí)對(duì)象不會(huì)被立刻銷(xiāo)毀。這一個(gè)好的C++編程習(xí)慣,但是它仍然是一個(gè)臨時(shí)對(duì)象,不能夠被修改。

              然而在C++11中,引進(jìn)了一種新的引用,即“右值引用”,允許綁定一個(gè)可變引用到一個(gè)右值,不是左值。換句話說(shuō),右值引用專(zhuān)注于檢測(cè)一個(gè)值是否為臨時(shí)對(duì)象。右值使用&&語(yǔ)法而不是&,可以是const和非const的,就像左值引用一樣,盡管你很少看到const左值引用。

            1 const string&& name = getName(); // ok 2 string&& name = getName(); // also ok - praise be!

              到目前為止一切都運(yùn)行良好,但這是如何實(shí)現(xiàn)的?左值引用和右值引用最重要的區(qū)別,是用著函數(shù)參數(shù)的左值和右值。看看如下兩個(gè)函數(shù):

            復(fù)制代碼
            復(fù)制代碼
            1 printReference (const String& str) 2 { 3 cout << str; 4 } 5 6 printReference (String&& str) 7 { 8 cout << str; 9 }
            復(fù)制代碼
            復(fù)制代碼

            這里函數(shù)printReference()的行為就有意思了:printReference (const String& str)接受任何參數(shù),左值和右值都可以,不管左值或右值是否為可變。printReference (String&& str)接受除可變右值引用的任何參數(shù)。換句話說(shuō),如下寫(xiě):

            1 string me( "alex" ); 2 printReference( me ); // calls the first printReference function, taking an lvalue reference 3 printReference( getName() ); // calls the second printReference function, taking a mutable rvalue reference

            現(xiàn)在我們應(yīng)該有一種方法來(lái)確定是否對(duì)臨時(shí)對(duì)象或非臨時(shí)對(duì)象使用引用。右值引用版本的方法就像進(jìn)入俱樂(lè)部(無(wú)聊的俱樂(lè)部,我猜的)的秘密后門(mén),如果是臨時(shí)對(duì)象,則只能進(jìn)。既然我們有方法確定一個(gè)對(duì)象是否為臨時(shí)對(duì)象,哪我們?cè)撊绾问褂媚兀?/p>

            move構(gòu)造函數(shù)和move賦值操作符

              當(dāng)你使用右值引用時(shí),最常見(jiàn)的模式是創(chuàng)建move構(gòu)造函數(shù)和move賦值操作符(遵循相同的原則)。move構(gòu)造函數(shù),跟拷貝構(gòu)造函數(shù)一樣,以一個(gè)實(shí)例對(duì)象作為參數(shù)創(chuàng)建一個(gè)新的基于原始實(shí)例對(duì)象的實(shí)例。然后move構(gòu)造函數(shù)可以避免內(nèi)存分配,因?yàn)槲覀冎浪呀?jīng)提供了一個(gè)臨時(shí)對(duì)象,而不是復(fù)制整個(gè)對(duì)象,只是“移動(dòng)”而已。假如我們有一個(gè)簡(jiǎn)單的ArrayWrapper類(lèi),如下:

            復(fù)制代碼
            復(fù)制代碼
             1 class ArrayWrapper 2 { 3 public: 4 ArrayWrapper (int n) 5 : _p_vals( new int[ n ] ) 6 , _size( n ) 7 {} 8 // copy constructor 9 ArrayWrapper (const ArrayWrapper& other) 10 : _p_vals( new int[ other._size ] ) 11 , _size( other._size ) 12 { 13 for ( int i = 0; i < _size; ++i ) 14 { 15 _p_vals[ i ] = other._p_vals[ i ]; 16 } 17 } 18 ~ArrayWrapper () 19 { 20 delete [] _p_vals; 21 } 22 private: 23 int *_p_vals; 24 int _size; 25 };
            復(fù)制代碼
            復(fù)制代碼

            注意,這里的復(fù)制拷貝構(gòu)造函數(shù)每次都會(huì)分配內(nèi)存和復(fù)制數(shù)組中的每個(gè)元素。對(duì)于復(fù)制操作是如此龐大的工作量,讓我們來(lái)添加move拷貝構(gòu)造函數(shù),獲得高效的性能。

            復(fù)制代碼
            復(fù)制代碼
             1 class ArrayWrapper 2 { 3 public: 4 // default constructor produces a moderately sized array 5 ArrayWrapper () 6 : _p_vals( new int[ 64 ] ) 7 , _size( 64 ) 8 {} 9 10 ArrayWrapper (int n) 11 : _p_vals( new int[ n ] ) 12 , _size( n ) 13 {} 14 15 // move constructor 16 ArrayWrapper (ArrayWrapper&& other) 17 : _p_vals( other._p_vals ) 18 , _size( other._size ) 19 { 20 other._p_vals = NULL; 21 } 22 23 // copy constructor 24 ArrayWrapper (const ArrayWrapper& other) 25 : _p_vals( new int[ other._size ] ) 26 , _size( other._size ) 27 { 28 for ( int i = 0; i < _size; ++i ) 29 { 30 _p_vals[ i ] = other._p_vals[ i ]; 31 } 32 } 33 ~ArrayWrapper () 34 { 35 delete [] _p_vals; 36 } 37 38 private: 39 int *_p_vals; 40 int _size; 41 };
            復(fù)制代碼
            復(fù)制代碼

               實(shí)際上move構(gòu)造函數(shù)比copy構(gòu)造函數(shù)更簡(jiǎn)單,這是相當(dāng)不錯(cuò)的。主要注意以下兩點(diǎn):

              1、參數(shù)是非const的右值引用

              2、other._p_vals應(yīng)置為NULL

              以上的第2點(diǎn)是對(duì)第1點(diǎn)的解釋?zhuān)慈绻覀兪褂胏onst右值引用,則不能將other._p_vals置為NULL。但為什么要將other._p_vals置為NULL呢?原因在于析構(gòu)函數(shù),當(dāng)臨時(shí)對(duì)象離開(kāi)其作用域,就像所有其他C++對(duì)象一樣,它們的析構(gòu)函數(shù)都會(huì)被調(diào)用。當(dāng)析構(gòu)函數(shù)被調(diào)用后, _p_vals將被釋放。這里我們只是復(fù)制了_p_vals,如果我們不將_p_vals置為NULL,move就不是真正的“移動(dòng)”,而是復(fù)制,一旦我們使用已釋放的內(nèi)存就會(huì)引發(fā)運(yùn)行奔潰。move構(gòu)造函數(shù)的意義在于,通過(guò)改變?cè)嫉呐R時(shí)對(duì)象來(lái)避免復(fù)制操作。

              再次重復(fù),重載move構(gòu)造函數(shù)是為了僅當(dāng)為臨時(shí)對(duì)象時(shí)move構(gòu)造函數(shù)才會(huì)被調(diào)用,只有臨時(shí)對(duì)象才能被修改。這意味著,如果函數(shù)的返回值是const對(duì)象,將調(diào)用copy構(gòu)造函數(shù),而不是move構(gòu)造函數(shù),所以不要像這樣寫(xiě):

            1 const ArrayWrapper getArrayWrapper (); // makes the move constructor useless, the temporary is const!

            有些情況如何在move構(gòu)造函數(shù)中我們還沒(méi)有討論,如類(lèi)中某個(gè)字段也是對(duì)象。觀察如下這個(gè)類(lèi):

            復(fù)制代碼
            復(fù)制代碼
             1 class MetaData 2 { 3 public: 4 MetaData (int size, const std::string& name) 5 : _name( name ) 6 , _size( size ) 7 {} 8 9 // copy constructor 10 MetaData (const MetaData& other) 11 : _name( other._name ) 12 , _size( other._size ) 13 {} 14 15 // move constructor 16 MetaData (MetaData&& other) 17 : _name( other._name ) 18 , _size( other._size ) 19 {} 20 21 std::string getName () const { return _name; } 22 int getSize () const { return _size; } 23 private: 24 std::string _name; 25 int _size; 26 };
            復(fù)制代碼
            復(fù)制代碼

            我們的數(shù)組有字段name和size,因此我們應(yīng)該改變ArrayWrapper的定義,如下:

            復(fù)制代碼
            復(fù)制代碼
             1 class ArrayWrapper 2 { 3 public: 4 // default constructor produces a moderately sized array 5 ArrayWrapper () 6 : _p_vals( new int[ 64 ] ) 7 , _metadata( 64, "ArrayWrapper" ) 8 {} 9 10 ArrayWrapper (int n) 11 : _p_vals( new int[ n ] ) 12 , _metadata( n, "ArrayWrapper" ) 13 {} 14 15 // move constructor 16 ArrayWrapper (ArrayWrapper&& other) 17 : _p_vals( other._p_vals ) 18 , _metadata( other._metadata ) 19 { 20 other._p_vals = NULL; 21 } 22 23 // copy constructor 24 ArrayWrapper (const ArrayWrapper& other) 25 : _p_vals( new int[ other._metadata.getSize() ] ) 26 , _metadata( other._metadata ) 27 { 28 for ( int i = 0; i < _metadata.getSize(); ++i ) 29 { 30 _p_vals[ i ] = other._p_vals[ i ]; 31 } 32 } 33 ~ArrayWrapper () 34 { 35 delete [] _p_vals; 36 } 37 private: 38 int *_p_vals; 39 MetaData _metadata; 40 };
            復(fù)制代碼
            復(fù)制代碼

              這樣就可以了??jī)H僅在ArrayWrapper中調(diào)用MetaData的move構(gòu)造函數(shù)就可以了,一切都很自然,不是么?問(wèn)題在于這樣做是不行的!原因很簡(jiǎn)單:move構(gòu)造函數(shù)中的other是右值引用。這里應(yīng)該是右值,而不是右值引用!如果是左值,則調(diào)用copy構(gòu)造函數(shù),而不是move構(gòu)造函數(shù)。有些奇怪,有點(diǎn)繞,對(duì)吧-我知道。這里有種方法可以區(qū)分:右值就是一個(gè)創(chuàng)建稍后會(huì)被銷(xiāo)毀的表達(dá)式。臨時(shí)對(duì)象即將被銷(xiāo)毀時(shí),我們將其傳入move構(gòu)造函數(shù)中,就相當(dāng)于給了它第二次生命,在新的作用域仍然有效。文中右值出現(xiàn)的地方,都是這么做的。在我們的構(gòu)造函數(shù)里,對(duì)象有一個(gè)name字段,它在函數(shù)內(nèi)部一直有效。換句話說(shuō),我們可以在函數(shù)中使用它多次,函數(shù)內(nèi)部定義的臨時(shí)變量在該函數(shù)內(nèi)部一直有效。左值是可以被定位的,我們可以在內(nèi)存某個(gè)位置訪問(wèn)一個(gè)左值。實(shí)際上,在函數(shù)中我們可能想稍后再使用它。如果move構(gòu)造被調(diào)用,這時(shí)我們就有一個(gè)右值引用對(duì)象,就可以使用“移動(dòng)的”對(duì)象了。

            復(fù)制代碼
            復(fù)制代碼
            1 // move constructor 2 ArrayWrapper (ArrayWrapper&& other) 3 : _p_vals( other._p_vals ) 4 , _metadata( other._metadata ) 5 { 6 // if _metadata( other._metadata ) calls the move constructor, using 7 // other._metadata here would be extremely dangerous! 8 other._p_vals = NULL; 9 }
            復(fù)制代碼
            復(fù)制代碼

            最后一種情況:左值和右值引用都是左值表達(dá)式。不用之處在于,左值引用必須是const綁定到右值,然而右值引用總是可以綁定一個(gè)引用到右值上。類(lèi)似于指針和指針?biāo)赶虻膬?nèi)容的區(qū)別。使用的值來(lái)至于右值,但是當(dāng)我們使用右值本身時(shí),它又成為左值。

            std::move

              那么有什么技巧可以處理這樣的情況?我們可以使用std::move,包含在<utility>中。如果你想將左值轉(zhuǎn)換為右值,可以使用std::move,這里std::move本身并不移動(dòng)任何東西,它只是將左值轉(zhuǎn)換成右值而已,也可以調(diào)用move構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)。請(qǐng)看如下代碼:

            復(fù)制代碼
            復(fù)制代碼
            1 #include <utility> // for std::move 2 // move constructor 3 ArrayWrapper (ArrayWrapper&& other) 4 : _p_vals( other._p_vals ) 5 , _metadata( std::move( other._metadata ) ) 6 { 7 other._p_vals = NULL; 8 }
            復(fù)制代碼
            復(fù)制代碼

            同樣的,也應(yīng)該修改MetaData:

            1 MetaData (MetaData&& other) 2 : _name( std::move( other._name ) ) // oh, blissful efficiency 3 : _size( other._size ) 4 {}

            賦值操作符

            如同move構(gòu)造函數(shù)一樣,我們也應(yīng)該有一個(gè)move賦值操作符,編寫(xiě)方式跟move構(gòu)造函數(shù)一樣。

            Move構(gòu)造函數(shù)和隱式構(gòu)造函數(shù)

               正如你所知道的,在C++中只要你手動(dòng)聲明了構(gòu)造函數(shù),編譯器就不會(huì)再為你產(chǎn)生默認(rèn)的構(gòu)造函數(shù)了。這里也是如此:為類(lèi)添加move構(gòu)造函數(shù)要求你定義和聲明一個(gè)默認(rèn)構(gòu)造函數(shù)。另外,聲明move構(gòu)造函數(shù)并不會(huì)阻止編譯器為你產(chǎn)生隱式的copy構(gòu)造函數(shù),聲明move賦值操作符也不會(huì)阻止編譯器創(chuàng)建標(biāo)準(zhǔn)的賦值操作符。

            std::move是如何工作的

              你或許會(huì)疑惑:如何編寫(xiě)一個(gè)類(lèi)似與std::move這樣的函數(shù)?右值引用轉(zhuǎn)換為左值引用是如何實(shí)現(xiàn)的?可能你已經(jīng)猜到答案了,就是typecasting。std::move的實(shí)際聲明比較復(fù)雜,但其核心思想就是static_cast到右值引用。這就意味著,實(shí)際上你并不真的需要使用move——但你應(yīng)該這樣做,這樣能夠更清楚表達(dá)你的意思。實(shí)際上轉(zhuǎn)換是必要,是件好事,這樣可以防止你意外地將左值轉(zhuǎn)換為右值,因?yàn)槟菢訉?dǎo)致意外的move發(fā)生,是相當(dāng)危險(xiǎn)的。你必須顯示地使用std::move(或者一個(gè)轉(zhuǎn)換)將左值轉(zhuǎn)換為右值引用,右值引用不會(huì)綁定它自己的左值上。

            函數(shù)返回顯式的右值引用

              什么時(shí)候時(shí)候你應(yīng)該寫(xiě)一個(gè)返回一個(gè)右值引用的函數(shù)?函數(shù)返回右值引用意味著什么呢?通過(guò)值返回對(duì)象的函數(shù)是不是就已經(jīng)是右值了?

              我們先回答第二個(gè)問(wèn)題:返回顯式的右值引用與通過(guò)值(by value)返回對(duì)象是不同的。讓我們看看下面的例子:

            復(fù)制代碼
            復(fù)制代碼
             1 int x; 2 3 int getInt () 4 { 5 return x; 6 } 7 8 int && getRvalueInt () 9 { 10 // notice that it's fine to move a primitive type--remember, std::move is just a cast 11 return std::move( x ); 12 }
            復(fù)制代碼
            復(fù)制代碼

            明顯在第一種情況里,盡管事實(shí)上getInt()是右值,但這里仍然對(duì)x執(zhí)行了copy操作。我們可以寫(xiě)個(gè)輔助函數(shù),看看:

            復(fù)制代碼
            復(fù)制代碼
            1 void printAddress (const int& v) // const ref to allow binding to rvalues 2 { 3 cout << reinterpret_cast<const void*>( & v ) << endl; 4 } 5 6 printAddress( getInt() ); 7 printAddress( x );
            復(fù)制代碼
            復(fù)制代碼

             運(yùn)行發(fā)現(xiàn),二者打印的x地址明顯不同。另一方面:

            1 printAddress( getRvalueInt() ); 2 printAddress( x );

            打印的x地址是相同的,這是因?yàn)間etRvalueInt()顯式的返回了一個(gè)右值。

            所以返回右值引用與不返回右值引用明顯是不同的。如果你返回已經(jīng)存在的對(duì)象,而不是在函數(shù)內(nèi)部創(chuàng)建的臨時(shí)對(duì)象(編譯器可能會(huì)為你做返回值優(yōu)化,避免copy操作)時(shí),這種不同表現(xiàn)得最為明顯。

            現(xiàn)在的問(wèn)題是,你是否需要這么做。答案是:很可能不會(huì)。大多數(shù)情況下,你最有可能得到一個(gè)懸空的(dangling)右值(一種情況是:引用存在,但它引用的臨時(shí)對(duì)象已經(jīng)銷(xiāo)毀了)。這種情況的危險(xiǎn)程度類(lèi)似于被引用的對(duì)象已經(jīng)不存在的左值。右值引用并不總是可以保證對(duì)象有效。返回右值引用主要使這種特殊情況有意義:你有一個(gè)成員函數(shù),該函數(shù)通過(guò)std::move返回類(lèi)中的字段。

            Move語(yǔ)義和標(biāo)準(zhǔn)庫(kù)

              回到最開(kāi)始的例子中,我們正在使用vector,但未控制類(lèi)vector,也不知道vector是否move構(gòu)造函數(shù)或move賦值操作符。幸運(yùn)地是標(biāo)準(zhǔn)委員會(huì)已經(jīng)將move語(yǔ)義添加到了標(biāo)準(zhǔn)庫(kù)中,這意味著你可以高效地返回vectors, maps, strings,以及你想要返回的任何標(biāo)準(zhǔn)庫(kù)對(duì)象,充分利用move語(yǔ)義吧。

            STL容器中可移動(dòng)的對(duì)象

              事實(shí)上,標(biāo)準(zhǔn)庫(kù)做得更加地好了。如果你在你的對(duì)象中通過(guò)創(chuàng)建move構(gòu)造函數(shù)和move賦值操作符來(lái)使用move語(yǔ)義,當(dāng)你將這些對(duì)象存儲(chǔ)在STL容器中,STL將自動(dòng)使用std::move,充分利用move語(yǔ)義為你避免效率底下的copy操作。

            posted on 2016-03-15 15:49 日需博客 閱讀(603) 評(píng)論(0)  編輯 收藏 引用 所屬分類(lèi): C C++技術(shù)文章
            亚洲AV无码成人网站久久精品大| 99久久精品免费看国产免费| 久久精品国产欧美日韩99热| 久久成人国产精品免费软件| 97久久综合精品久久久综合| 精品久久久久中文字| 亚洲中文字幕久久精品无码喷水| www久久久天天com| 久久久无码精品午夜| 久久人与动人物a级毛片| 久久久青草青青国产亚洲免观| 丁香五月综合久久激情| 亚洲AV无码久久精品色欲| 久久伊人精品一区二区三区| 久久久久久综合一区中文字幕 | 久久久噜噜噜久久熟女AA片| 国产精品欧美久久久久天天影视| 岛国搬运www久久| 一级做a爰片久久毛片免费陪| 狠狠狠色丁香婷婷综合久久俺| yy6080久久| 久久精品无码专区免费青青| 国产精品久久网| 久久精品无码一区二区app| 韩国无遮挡三级久久| 久久精品视频91| 亚洲人成伊人成综合网久久久| 久久香蕉超碰97国产精品| 一本色道久久88—综合亚洲精品| 精品久久8x国产免费观看| 国产成人99久久亚洲综合精品| 香蕉久久夜色精品国产2020 | 色综合合久久天天综合绕视看| 久久久网中文字幕| 久久久久久国产精品无码超碰| 国内精品久久久久久久亚洲| 久久无码AV中文出轨人妻| 久久久久久免费一区二区三区 | 久久777国产线看观看精品| 久久久久亚洲AV综合波多野结衣| 久久人人爽爽爽人久久久|