<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      【譯】VC10中的C++0x特性 Part 2 (1):右值引用

      【譯】VC10中的C++0x特性 Part 2 (1):右值引用

      原文來源:vcblog 翻譯:羅朝輝 (http://www.rzrgm.cn/kesalin/)
      本文遵循“署名-非商業(yè)用途-保持一致”創(chuàng)作公用協(xié)議

       

      簡介

      這一系列文章介紹Microsoft Visual Studio 2010 中支持的C++ 0x特性,目前有三部分。 
      Part 1 :介紹了Lambdas, 賦予新意義的auto,以及 static_assert; 
      Part 2( 1 , 2 ):介紹了右值引用(Rvalue References); 
      Part 3 :介紹了表達式類型(decltype)

      VC10中的C++0x特性 Part 1,2,3 譯文打包下載(doc 和 pdf 格式): 點此下載

      本文是 Part 2 的第一頁。

      今天我要講的是 rvalue references (右值引用),它能實現(xiàn)兩件不同的事情: move 語意和完美轉(zhuǎn)發(fā)。剛開始會覺得它們難以理解,因為需要區(qū)分 lvalues 和 rvalues ,而只有極少數(shù) C++98/03 程序員對此非常熟悉。這篇文章會很長,因為我打算極其詳盡地解釋 rvalue references 的運作機制。

      不用害怕,使用 ravlue references 是很容易的,比聽起來要容易得多。要在你的代碼中實現(xiàn) move semantics 或 perfect forwarding 只需遵循簡單的模式,后文我會對此作演示的。學習如何使用 rvalue references 是絕對值得的,因為 move semantics 能帶來巨大的性能提升,而 perfect forwarding 讓高度泛型代碼的編寫變得非常容易。

       

      C++ 98/03 中的 lvalues 和 rvalues

      要理解C++ 0x中的 rvalue references,你得先理解 C++ 98/03 中的 lvalues 與 rvalues。

      術語 “l(fā)values” 和 “rvalues” 是很容易被搞混的,因為它們的歷史淵源也是混淆。(順帶一提,它們的發(fā)音是 ‘L values“ 和  ”R values“, 盡管它們都寫成一個單詞)。這兩個概念起初來自 C,后來在 C++ 中被加以發(fā)揮。為節(jié)省時間,我跳過了有關它們的歷史,比如為什么它們被稱作 “l(fā)values” 和 “rvalues”,我將直接講它們在 C++ 98/03 中是如何運作的。(好吧,這不是什么大秘密: “L” 代表 “l(fā)eft”,“R” 代表 “right”。它們的含義一直在演化而名字卻沒變,現(xiàn)在已經(jīng)“名”不副“實”了。與其幫你上一整堂歷史課,不如隨意地把它們當作像“上夸克”和“下夸克 ”之類的名字,也不會有什么損失。)

      C++ 03 標準 3.10/1 節(jié)上說: “每一個表達式要么是一個 lvalue ,要么就是一個 rvalue 。” 應該謹記 lvalue 跟 rvalue 是針對表達式而言的,而不是對象。

      lvalue 是指那些單一表達式結(jié)束之后依然存在的持久對象。例如: obj,*ptr, prt[index], ++x 都是 lvalue。

      rvalue 是指那些表達式結(jié)束時(在分號處)就不復存在了的臨時對象。例如: 1729 , x + y , std::string("meow") , 和 x++ 都是 rvalue。

      注意 ++x 和 x++ 的區(qū)別。當我們寫 int x = 0; 時, x 是一個 lvalue,因為它代表一個持久對象。 表達式 ++x 也是一個 lvalue,它修改了 x 的值,但還是代表原來那個持久對象。然而,表達式 x++ 卻是一個 rvalue,它只是拷貝一份持久對象的初值,再修改持久對象的值,最后返回那份拷貝,那份拷貝是臨時對象。 ++x 和 x++ 都遞增了 x,但 ++x 返回持久對象本身,而 x++ 返回臨時拷貝。這就是為什么 ++x 之所以是一個 lvalue,而 x++ 是一個 rvalue。 lvalue 與 rvalue 之分不在于表達式做了什么,而在于表達式代表了什么(持久對象或臨時產(chǎn)物)。

      另一個 培養(yǎng)判斷一個表達式是不是 lvalue 的直覺感的方法就是自問一下“我能不能對表達式取址?”,如果能夠,那就是一個 lvalue;如果不能,那就是 一個 rvalue。 例如:&obj , &*ptr , &ptr[index] , 和 &++x 都是合法的(即使其中一些例子很蠢),而 &1729 , &(x + y) , &std::string("meow") , 和 &x++ 是不合法的。為什么這個方法湊效?因為取址操作要求它的“操作數(shù)必須是一個 lvalue”(見 C++ 03 5.3.1/2)。為什么要有那樣的規(guī)定?因為對一個持久對象取址是沒問題的,但對一個臨時對象取址是極端危險的,因為臨時對象很快就會被銷毀(譯注:就像你有一個指向某個對象的指針,那個對象被釋放了,但你還在使用那個指針,鬼知道這時候指針指向的是什么東西)。

      前面的例子不考慮操作符重載的情況,它只是普通的函數(shù)調(diào)用語義。“一個函數(shù)調(diào)用是一個 lvalue 當且僅當它返回一個引用”(見 C++ 03 5.2.2/10)。因此,給定語句 vercor<int> v(10, 1729); , v[0] 是一個 lvalue,因為操作符 []() 返回 int& (且 &v[0] 是合法可用的); 而給定語句 string s("foo");和 string t("bar");,s + t 是一個rvalue,因為操作符 +() 返回 string(而 &(s + t) 也不合法)。

      lvalue 和 rvalue 兩者都有非常量(modifiable,也就是說non-const)與常量(const )之分。舉例來說:

      string one("cute");

      const string two("fluffy");

      string three() { return "kittens"; }

      const string four() { return "are an essential part of a healthy diet"; }

      one;     // modifiable lvalue

      two;     // const lvalue

      three(); // modifiable rvalue

      four();  // const rvalue

      Type& 可綁定到非常量 lvalue (可以用這個引用來讀取和修改原來的值),但不能綁定到 const lvalue,因為那將違背 const 正確性;也不能把它綁定到非常量 rvalue,這樣做極端危險,你用這個引用來修改臨時對象,但臨時對象早就不存在了,這將導致難以捕捉而令人討厭的 bug,因此 C++ 明智地禁止這這么做。(我要補充一句:VC 有一個邪惡的擴展允許這么蠻干,但如果你編譯的時候加上參數(shù) /W4 ,編譯器通常會提示警告"邪惡的擴展被激活了”)。也不能把它綁定到 const ravlue,因為那會是雙倍的糟糕。(細心的讀者應該注意到了我在這里并沒有談及模板參數(shù)推導)。

      const Type& 可以綁定到: 非常量 lvalues, const lvalues,非常量 rvalues 以及 const values。(然后你就可以用這個引用來觀察它們)

      引用是具名的,因此一個綁定到 rvalue 的引用,它本身是一個 lvalue(沒錯!是 L)。(因為只有 const 引用可以綁定到 rvalue,所以它是一個 const lvalue)。這讓人費解,(不弄清楚的話)到后面會更難以理解,因此我將進一步解釋。給定函數(shù) void observe(const string& str), 在 observe()'s 的實現(xiàn)中, str 是一個 const lvalue,在 observe() 返回之前可以對它取址并使用那個地址。這一點即使我們通過傳一個 rvalue 參數(shù)來調(diào)用 observe()也是成立的 ,就像上面的 three() 和 four()。也可以調(diào)用 observe("purr"),它構(gòu)建一個臨時 string 并將 str 綁定到那個臨時 string。three() 和 foure() 的返回對象是不具名的,因此他們是 rvalue,但是在 observe()中,str 是具名的,所以它是一個 lvalue。正如前面我說的“ lvalue 跟 rvalue 是針對表達式而言的,而不是對象”。當然,因為 str 可以被綁定到一個很快會被銷毀的臨時對象,所以在 observe() 返回之后我們就不應該在任何地方保存這個臨時對象的地址。

      你有沒有對一個綁定到 rvalue 的 const 引用取址過么?當然,你有過!每當你寫一個帶自賦值檢查的拷貝賦值操作符: Foo& operator=(const Foo& other), if( this != &other) { copy struff;}; 或從一個臨時變量來拷貝賦值,像: Foo make_foo(); Foo f; f = make_foo(); 的時候,你就做了這樣的事情。

      這個時候,你可能會問“那么非常量 rvalues 跟 const rvalues 有什么不同呢?我不能將 Type& 綁定到非常量 rvalue 上,也不能通過賦值等操作來修改 rvalue,那我真的可以修改它們?” 問的很好!在 C++ 98/03 中,這兩者存在一些細微的差異: non-constrvalues 可以調(diào)用 non-const 成員函數(shù)。 C++ 不希望你意外地修改臨時對象,但直接在non-const rvalues上調(diào)用 non-const 成員函數(shù),這樣做是很明顯的,所以這是被允許的。在 C++ 0x中,答案有了顯著的變化,它能用來實現(xiàn) move 語意。 

      恭喜!你已經(jīng)具備了我所謂的“l(fā)value/rvalue 觀”,這樣你就能夠一眼就判斷出一個表達式到底是 lvalue 還是 rvalue。再加上你原來對 const 的認識,你就能完全理解為什么給定語句 void mutate(string& ref) 以及前面的變量定義, mutate(one) 是合法的,而 mutate(two), mutate(three()), mutate(four()), mutate("purr") 都是不合法的。如果你是 C++ 98/03 程序員,你已經(jīng)可以分辨出這些調(diào)用中的哪些是合法的,哪些是不合法的;是你的“本能直覺”,而不是你的編譯器,告訴你 mutate(three()) 是假冒的。你對 lvalue/rvalue 的新認識讓你明確地理解為什么 three() 是一個 rvalue,也知道為什么非常量引用不能綁定到右值。知道這些有用么?對語言律師而言,有用,但對普通程序員來說并不見得。畢竟,你如果不理解關于 lvalues 和 rvalues 一切就要領悟這個還隔得遠呢。但是重點來了:與 C++ 98/03 相比, C++ 0x 中的 lvalue 和 rvalue 有著更廣泛更強勁的含義(尤其是判斷表達式是否是 modifiable / const 的 lvalue/rvalue,并據(jù)此做些處理)。要有效地使用 C++ 0x,你也需具備對 lvalue/rvalue 的理解。現(xiàn)在萬事具備,我們能繼續(xù)前行了。

       

      拷貝的問題 

      C++ 98/03 將不可思議的高度抽象和不可思議的高效執(zhí)行結(jié)合到了一起,但有個問題:它過度濫用拷貝。對行為像 int 那樣有著值語意的對象而言,源對象的拷貝是獨立存在的,并不會影響源對象。值語意很好,除了在會導致冗余拷貝之外,像拷貝 strings,vectors 等重型對象那樣的情況。(“重型”意味著“昂貴的拷貝開銷”;有著100萬個元素的 vector 是重型的)。返回值優(yōu)化(RVO) 和命名返回值優(yōu)化(NRVO)在特定情況下可以優(yōu)化掉拷貝構(gòu)造操作,這有助于減緩問題的嚴重性,但是它們不能夠消除所有冗余的拷貝。

      最最沒有必要的拷貝是拷貝那些立馬會被銷毀的對象。你有過復印一份文件,并馬上把原件扔掉的經(jīng)歷么(假定原件和復件是相同的)?那簡直是浪費,你應該持有原件而不必費勁去復印。下面是被我稱作“殺手級的示例”,來自標準委員會的例子(見提案 N1377),假設你有一大堆 string 像這樣的: 

      string s0("my mother told me that");

      string s1("cute");

      string s2("fluffy");

      string s3("kittens");

      string s4("are an essential part of a healthy diet");

      然后你想像這樣把它們串接起來:

      string dest = s0 + " " + s1 + " " + s2 + " " + s3 + " " + s4;

      這樣做的效率如何?(我們不用為這個特殊的例子而擔憂,它的執(zhí)行只要幾微秒;我們擔憂它的一般化情況,在語言層面上的情況)。

      每次調(diào)用操作符 +() 就會返回一個臨時 string。上面調(diào)用了 8 次操作符 +(),因而產(chǎn)生了 8 個臨時 string。 每一個臨時 string,在構(gòu)造過程中分配動態(tài)內(nèi)存,再拷貝所有已連接的字符,最后在析構(gòu)過程中釋放分配的動態(tài)內(nèi)存。(你聽說過短串優(yōu)化技術么,為了避免動態(tài)內(nèi)存的分配與釋放,VC是這么干的,在這個被我精心挑選的有著合適長度的 s0 面前短串優(yōu)化技術也無能為力,即使執(zhí)行了這樣的優(yōu)化,也無法避免拷貝操作。如果你還聽說過寫時拷貝優(yōu)化(Copy - On - Write),忘了它吧,在這里也不適用,并且在多線程環(huán)境下這種優(yōu)化會惡化問題,因此標準庫實現(xiàn)根本就不再做這個優(yōu)化了)。

      事實上,因為每一個串接操作都會拷貝所有已經(jīng)串接好的字符,所以那個復雜度是字符串長度的平方了。哎呀!這太浪費了!這點確實讓 C++ 尷尬。事情怎么會搞成這樣呢?有沒有改善的辦法?

      問題是這樣的,operator+()接受兩個參數(shù),一個是 const string&,另一個是 const string& 或 const char * (還有其他重載版本,但在這里我們沒有用到),但 operator+() 無法分辨出你塞給它的是 lvalue 還是 rvalue 參數(shù),所以它只好總是創(chuàng)建一個臨時 string,并返回這個臨時 string。為什么這跟 vavlue/rvalue 有關系?

      當我們要計算 s0 + " " 的值時,很明顯這里有必要創(chuàng)建一個新的臨時 string。 s0 是一個 lvalue,它已經(jīng)命名了一個持久對象,因此我們不能修改它。(有人注意到了!) 。如果要計算 (s0 + “ ”) + s1 的值,我們可以簡單地將 s1 的內(nèi)容追加到第一個臨時 string 上,而不用創(chuàng)建第二個臨時 string 再把第一個丟棄掉。這就是 move 語意背后的核心觀念: 因為 s0 + " " 是一個 rvalue ,只有那個在整個程序中唯一能夠覺察到臨時對象存在的表達式可以引用臨時對象。如果我們能檢測到表達式是一個非常量 rvalue,我們就可以任意修改臨時對象,而不會有人發(fā)現(xiàn)。 操作符 +() 本不應該修改它的參數(shù),但如果其參數(shù)是非常量 rvalue,誰在乎?照這種方法,每次調(diào)用操作符 +() 都把字符追加到唯一的臨時對象上,這樣就徹底省掉了不必要的動態(tài)內(nèi)存管理和冗余的拷貝操作,呈現(xiàn)出線性復雜度。耶!

      從技術上講,在 C++ 0x 中,每次調(diào)用操作符 +() 還是會返回一個單獨的臨時 string。 然而,第二個臨時 string (產(chǎn)生自 (s0 + “ ”) + s1 )可以通過“竊取”第一個臨時 string (產(chǎn)生自 s0 + " "  )的內(nèi)存而被構(gòu)造出來,然后再把 s1 的內(nèi)容追加到那塊內(nèi)存后面(這將會引發(fā)一個普通的重分配操作)。“竊取”是通過指針的操作實現(xiàn)的:第二個臨時 string 會先拷貝第一個臨時 string 的內(nèi)部指針,然后再清空這個指針。第一個臨時 string 最后被銷毀(在分號那地方)時,它的指針已經(jīng)置為 null 了,因此它的析構(gòu)函數(shù)什么也不會做(譯注:也就是說不會釋放它的內(nèi)存,這部分內(nèi)存現(xiàn)在是第二個臨時 string 在使用了)。

      通常,如果能夠檢測到非常量 rvalue,你就能夠做些“資源竊取”的優(yōu)化。如果非常量 rvalue 所引用的那些對象持有任何資源(如內(nèi)存),你就能竊取它們的資源而不用拷貝它們,反正它們很快就會被銷毀掉。通過竊取非常量 rvalue 持有的資源來構(gòu)建或賦值的手法通常被稱作 “moving”,可移動對象擁有 “move 語意”。

      在大多數(shù)情況下這相當有用,比如 vector 的重新分配。當一個 vector 需要更多空間(如 push_back() 時)和進行重分配操作時,它需要從舊的內(nèi)存塊中拷貝元素到新的內(nèi)存塊中去。這些拷貝構(gòu)造調(diào)用的開銷很大。(對 vector<string> 來說,需要拷貝每一個 string 元素,這涉及動態(tài)內(nèi)存分配)。但是等一等!舊內(nèi)存塊中的那些元素很快會被銷毀掉的呀,所以我們可以挪動這些元素,而不用拷貝它們。在這種情形下,舊內(nèi)存塊中的元素依然存在于內(nèi)存中,用來訪問它們的表達式,如 old_ptr[index],還是 lvalue。在重分配過程中,我們想用非常量 rvalue 表達式來引用舊內(nèi)存塊中的元素。假定它們是非常量 rvalue,那我們就能夠移動它們,從而省去拷貝構(gòu)造開銷。(說”我想假定這個 lvalue 是一個非常量 rvalue “等同于說”我知道這是一個 lvalue,它指向一個持久對象,但我不關心隨后會對這個 lvalue 進行怎樣的操作,或銷毀它,或給它賦值,或進行任意操作。因此如果你能從它那里竊取資源的話,盡管行動吧”)

      C++0x 的 rvalue 引用概念給與我們檢測非常量 rvalue 并從中竊取資源的能力,這讓我能夠?qū)崿F(xiàn) move 語意。rvalue 引用也讓我們能夠通過把 lvalue 偽裝成非常量 rvalue 而隨意觸發(fā) move 語意。現(xiàn)在,我們來看看 rvalue 引用是如何工作的!

       

      ravlue 引用:初始化

      C++0x 引進了一種新的引用,ravlue 引用,其語法是 Type&& 和 const Type&& 。目前 C++0x 草案 N2798 8.3.2/2 上說:“用 & 聲明的引用類型被稱作 lvalue 引用,而用 && 聲明的引用類型被稱作 rvalue 引用。lvalue 引用與 rvalue 引用是截然不同的類型。除非特別注明,兩者在語意上是相當?shù)牟⑶乙话愣急环Q作引用。”這意味著對 C++98/03 中引用(即現(xiàn)在的 lvalue 引用)的直覺印象可以延伸用于 rvalue 引用;你只需要學習這兩者的不同之處。 

      (說明:我選擇把 Type& 讀作 “Type ref”,Type&& 讀作 "Type ref ref"。它們的全稱分別是 “l(fā)value reference to Type” 和 "rvalue reference to Type",就像 “cosnt pointer to int” 被寫成 “int * const”,而被讀作 “int star const”一樣。)

      兩者有什么區(qū)別?與 lvalue 引用相比, rvalue 引用在初始化與重載決議時表現(xiàn)出不同的行為。兩者的區(qū)別在于它們會優(yōu)先綁定到什么東西上(初始化時)和什么東西會優(yōu)先綁定到它們身上(重載決議時)。首先讓我們來看看初始化:

      · 我們已經(jīng)明白為何非常量 lvalue 引用( Type& ) 只能綁定到非常量 lvalue 上,而其他的一概不能(如 const lvalues,非常量 rvalues,const rvalues)

      · 我們已經(jīng)明白為何 const lvalue 引用( const Type& ) 能綁定到任何東西上。

      · 非常量 rvalue ( Type&& ) 能夠綁定到非常量 lvalue 以及非常量 rvalue 上,而不能綁定到 const lvalues 和 const rvalues (這會違背 const 正確性)

      · const rvalue 引用( const Type&& ) 能夠綁定到任何東西上。

       

      這些規(guī)則聽起來可能有些神秘,但是他們來源于兩條簡單的規(guī)則:

      · 遵守 const 正確性,所以你不能把非常量引用綁定到常量上。

      · 避免意外修改臨時對象,所以你不能把非常量 lvalue 引用綁定到非常量 rvalue 上來。

       

      如果你更喜歡閱讀編譯器錯誤信息,而不是閱讀文字描述,下面是一個示例:

       

      C:/Temp>type initialization.cpp

      #include <string>

      using namespace std;

       

      string modifiable_rvalue() {

          return "cute";

      }

      const string const_rvalue() {

          return "fluffy";

      }

      int main() {

          string modifiable_lvalue("kittens");

          const string const_lvalue("hungry hungry zombies");

       

          string& a = modifiable_lvalue;          // Line 16

          string& b = const_lvalue;               // Line 17 - ERROR

          string& c = modifiable_rvalue();        // Line 18 - ERROR

          string& d = const_rvalue();             // Line 19 - ERROR

       

          const string& e = modifiable_lvalue;    // Line 21

          const string& f = const_lvalue;         // Line 22

          const string& g = modifiable_rvalue();  // Line 23

          const string& h = const_rvalue();       // Line 24

       

          string&& i = modifiable_lvalue;         // Line 26

          string&& j = const_lvalue;              // Line 27 - ERROR

          string&& k = modifiable_rvalue();       // Line 28

          string&& l = const_rvalue();            // Line 29 - ERROR

       

          const string&& m = modifiable_lvalue;   // Line 31

          const string&& n = const_lvalue;        // Line 32

          const string&& o = modifiable_rvalue(); // Line 33

          const string&& p = const_rvalue();      // Line 34

      }

       

      C:/Temp>cl /EHsc /nologo /W4 /WX initialization.cpp

      initialization.cpp

      initialization.cpp(17) : error C2440: 'initializing' : cannot convert from 'const std::string' to 'std::string &'

              Conversion loses qualifiers

      initialization.cpp(18) : warning C4239: nonstandard extension used : 'initializing' : conversion from 'std::string' to 'std::string &'

              A non-const reference may only be bound to an lvalue

      initialization.cpp(19) : error C2440: 'initializing' : cannot convert from 'const std::string' to 'std::string &'

              Conversion loses qualifiers

      initialization.cpp(27) : error C2440: 'initializing' : cannot convert from 'const std::string' to 'std::string &&'

              Conversion loses qualifiers

      initialization.cpp(29) : error C2440: 'initializing' : cannot convert from 'const std::string' to 'std::string &&'

              Conversion loses qualifiers

       

      非常量 rvalue 引用綁定到非常量 rvalue 是沒問題的;要領就是它們可以被用來修改臨時對象。

      雖然 lvalue 引用和 rvalue 引用在初始化時有著相似的行為(只有第 18 和 28 行不同),但在重載決議的時候它們的區(qū)別就很顯著了。

       

      rvalue 引用:重載決議

      函數(shù)可根據(jù)非常量和常量 lvalue 引用參數(shù)的不同而重載,這一點你應該很熟悉了。在 C++0x 中,函數(shù)也可根據(jù)非常量和常量 rvalue 引用參數(shù)的不同而重載。如果給出這四種形式的重載一元函數(shù),你不應為表達式能優(yōu)先綁定到與之相對應的引用上而決議出相應的重載函數(shù)這一點感到驚奇:

      C:/Temp>type four_overloads.cpp

      #include <iostream>

      #include <ostream>

      #include <string>

      using namespace std;

       

      void meow(string& s) {

          cout << "meow(string&): " << s << endl;

      }

       

      void meow(const string& s) {

          cout << "meow(const string&): " << s << endl;

      }

      void meow(string&& s) {

          cout << "meow(string&&): " << s << endl;

      }

      void meow(const string&& s) {

          cout << "meow(const string&&): " << s << endl;

      }

      string strange() {

          return "strange()";

      }

      const string charm() {

          return "charm()";

      }

      int main() {

          string up("up");

          const string down("down");

          meow(up);

          meow(down);

          meow(strange());

          meow(charm());

      C:/Temp>cl /EHsc /nologo /W4 four_overloads.cpp

      four_overloads.cpp

      C:/Temp>four_overloads

      meow(string&): up

      meow(const string&): down

      meow(string&&): strange()

      meow(const string&&): charm()

       

      在實踐中,全部重載 Type& , const Type& , Type&& , const Type&& 并不是很有用。只重載 const Type& 和 Type&& 更有意思些:

      C:/Temp>type two_overloads.cpp

      #include <iostream>

      #include <ostream>

      #include <string>

      using namespace std;

      void purr(const string& s) {

          cout << "purr(const string&): " << s << endl;

      }

      void purr(string&& s) {

          cout << "purr(string&&): " << s << endl;

      }

      string strange() {

          return "strange()";

      }

      const string charm() {

          return "charm()";

      }

      int main() {

          string up("up");

          const string down("down");

          purr(up);

          purr(down);

          purr(strange());

          purr(charm());

      }

      C:/Temp>cl /EHsc /nologo /W4 two_overloads.cpp

      two_overloads.cpp

      C:/Temp>two_overloads

      purr(const string&): up

      purr(const string&): down

      purr(string&&): strange()

      purr(const string&): charm()

       

      上面的重載決議是怎么作出的呢?下面是規(guī)則:

      (1) 初始化規(guī)則擁有否決權(quán)。

      (2) lvalue 最優(yōu)先綁定到 lvalue 引用,rvalue 最優(yōu)先綁定到 rvalue 引用。

      (3) 非常量表達式傾向于綁定到非常量引用上。

      (我說的“否決權(quán)”是指:進行重載決議時初始化規(guī)則否決那些不可行(譯注:不滿足 const 正確性)的候選函數(shù),這些函數(shù)阻止將表達式綁定到引用上) 讓我們一條一條來看看這些規(guī)則是怎么運作的。

      ·對 purr(up) 而言,決議(1)初始化規(guī)則既不否決 purr(const string&) 也不否決 purr(string&&)。 up 是 lvalue,因此滿足決議(2)中的 lvalue 最優(yōu)先綁定到 lvalue 引用,即 purr(const string&)。up 還是非常量,因此滿足決議(3)非常量表達式傾向于綁定到非常量引用上,即purr(string&&)。兩者放一塊決議時,決議(2)勝出,選擇 purr(const string&)。

      ·對 purr(down) 而言, 決議(1)初始化規(guī)則基于 const 正確性否決掉 purr(string&&),因此 purr(const string&) 勝出。

      ·對 purr(strange()) 而言,決議(1)初始化規(guī)則既不否決 purr(const string&) 也不否決 purr(string&&)。strange() 是 rvalue, 因此滿足決議(2) rvalue 最優(yōu)先綁定到 rvalue 引用,即 purr(string&&)。strange() 還是非常量,因此滿足決議(3)非常量表達式傾向于綁定到非常量引用上,即purr(string&&)上。 purr(string&&) 在這里兩票勝出。

      ·對 purr(charm()) 而言,決議(1)初始化規(guī)則基于 const 正確性否決掉 purr(string&&),因此 purr(const string&) 勝出。

       

      值得注意的是當你只重載了const Type& 和 Type&& ,非常量 rvalue 綁定到 Type&&,而其它的都綁定到 const Type&。因此,這一組重載用來實現(xiàn) move 語義。

      重要說明:返回值的函數(shù)應當返回 Type(如 strange() )而不是返回 const Type (如 charm())。后者不會帶來什么好處(阻止非常量成員函數(shù)調(diào)用),還會阻止 move 語意優(yōu)化。

       

      move 語義:模式

      下面是一個簡單的類 remote_integer, 內(nèi)部存儲一個指向動態(tài)分配的 int 指針(“遠程擁有權(quán)”)。你應該對這個類的默認構(gòu)造函數(shù),一元構(gòu)造函數(shù),拷貝構(gòu)造函數(shù),拷貝賦值函數(shù)和析構(gòu)函數(shù)都很熟悉了。我給它增加了 move 構(gòu)造函數(shù)和 move 賦值函數(shù),它們被#ifdef MOVABLE 圍起來了,這樣我就可以演示在有和沒有這兩個函數(shù)的情況下會有什么差別,在真實的代碼中是不會這么做的。 

      C:/Temp>type remote.cpp

      #include <stddef.h>

      #include <iostream>

      #include <ostream>

      using namespace std;

      class remote_integer {

      public:

          remote_integer() {

              cout << "Default constructor." << endl;

              m_p = NULL;

          }

          explicit remote_integer(const int n) {

              cout << "Unary constructor." << endl;

              m_p = new int(n);

          } 

          remote_integer(const remote_integer& other) {

              cout << "Copy constructor." << endl;

              if (other.m_p) {

                  m_p = new int(*other.m_p);

              } else {

                  m_p = NULL;

              }

          }

       

      #ifdef MOVABLE

          remote_integer(remote_integer&& other) {

              cout << "MOVE CONSTRUCTOR." << endl;

              m_p = other.m_p;

              other.m_p = NULL;

          }

      #endif // #ifdef MOVABLE

           remote_integer& operator=(const remote_integer& other) {

              cout << "Copy assignment operator." << endl; 

              if (this != &other) {

                  delete m_p;

                  if (other.m_p) {

                      m_p = new int(*other.m_p);

                  } else {

                      m_p = NULL;

                  }

              } 

              return *this;

          }

       

      #ifdef MOVABLE

          remote_integer& operator=(remote_integer&& other) {

              cout << "MOVE ASSIGNMENT OPERATOR." << endl;

              if (this != &other) {

                  delete m_p;

                  m_p = other.m_p;

                  other.m_p = NULL;

              } 

              return *this;

          }

      #endif // #ifdef MOVABLE

       

          ~remote_integer() {

              cout << "Destructor." << endl;

              delete m_p;

          }

          int get() const {

              return m_p ? *m_p : 0;

          }

      private:

          int * m_p;

      };

       

      remote_integer square(const remote_integer& r) {

          const int i = r.get(); 

          return remote_integer(i * i);

      }

      int main() {

          remote_integer a(8);

          cout << a.get() << endl;

          remote_integer b(10);

          cout << b.get() << endl;

          b = square(a);

          cout << b.get() << endl;

      }

      C:/Temp>cl /EHsc /nologo /W4 remote.cpp

      remote.cpp

      C:/Temp>remote

      Unary constructor.

      8

      Unary constructor.

      10

      Unary constructor.

      Copy assignment operator.

      Destructor.

      64

      Destructor.

      Destructor.

       

      C:/Temp>cl /EHsc /nologo /W4 /DMOVABLE remote.cpp

      remote.cpp

       

      C:/Temp>remote

      Unary constructor.

      8

      Unary constructor.

      10

      Unary constructor.

      MOVE ASSIGNMENT OPERATOR.

      Destructor.

      64

      Destructor.

      Destructor.

       

      這里有幾點值得注意:

      ·我們重載了拷貝構(gòu)造函數(shù)和 move 構(gòu)造函數(shù),還重載了拷貝賦值函數(shù)和 move 賦值函數(shù)。在前面我們已經(jīng)看到了當函數(shù)通過 const Type& 和 Type&& 進行重載時,會有怎樣的結(jié)果。當 move 語意可用時,b = square(a) 會自動選擇調(diào)用 move 賦值函數(shù)。

      ·move 構(gòu)造函數(shù)和 move 賦值函數(shù)只是簡單的從 other 那里“竊取”內(nèi)存,而不用動態(tài)分配內(nèi)存。當“竊取”內(nèi)存時,我們只是拷貝 other 的指針成員,然后再把它置為 null。于是當 other 被銷毀時,析構(gòu)函數(shù)什么也不做。

      ·拷貝賦值函數(shù)和 move 賦值函數(shù)都需要進行自我賦值檢查,為何拷貝賦值函數(shù)需要進行自我賦值檢查是廣為人知的。這是因為像 int 這樣的內(nèi)建數(shù)據(jù)(POD)類型能夠正確地自我賦值(如:x = x ),因此,用戶自定義的數(shù)據(jù)類型理應也可以正確地自我賦值。自我賦值實際上在手寫代碼里面是不存在的,但是在類似 std::sort() 之類的算法中,卻很常見。在 C++0x 中,像 std::sort() 之類的算法能夠通過挪動而非拷貝元素來實現(xiàn)。在這里(move 賦值函數(shù))也需要進行自我賦值檢查。

       

      這時,你可能會想它們( move 拷貝構(gòu)造函數(shù)和 move 賦值函數(shù))與編譯器自動生成(標準中用詞“隱式聲明”)的默認拷貝構(gòu)造函數(shù)和默認賦值函數(shù)有什么相互影響呢。 

      ·永遠不會自動生成 move 構(gòu)造函數(shù)和 move 賦值函數(shù)。

      ·用戶聲明的構(gòu)造函數(shù),拷貝構(gòu)造函數(shù)和 move 構(gòu)造函數(shù)會抑制住默認構(gòu)造函數(shù)的自動生成。

      ·用戶聲明的拷貝構(gòu)造函數(shù)會抑制住默認拷貝構(gòu)造函數(shù)的自動生成,但是用戶聲明的 move 構(gòu)造函數(shù)做不到。

      ·用戶聲明的拷貝賦值函數(shù)會抑制住默認拷貝賦值函數(shù)的自動生成,但是用戶聲明的 move 賦值函數(shù)做不到。

      基本上,除了聲明 move 構(gòu)造函數(shù)會抑制默認構(gòu)造函數(shù)的自動生成以外,自動生成規(guī)則不影響 move 語義。

      轉(zhuǎn)載時請注明作者和出處。未經(jīng)許可,請勿用于商業(yè)用途)


      posted @ 2009-06-01 20:43  飄飄白云  閱讀(493)  評論(0)    收藏  舉報
      本博客遵循 Creative Commons License “署名-非商業(yè)用途-保持一致”創(chuàng)作共用協(xié)議。 與我聯(lián)系
      主站蜘蛛池模板: 日韩内射美女人妻一区二区三区| 久久一日本综合色鬼综合色| 草裙社区精品视频播放| 亚洲国产精品日韩专区av| 国产人妻丰满熟妇嗷嗷叫| 乱女乱妇熟女熟妇综合网| 亚洲男女羞羞无遮挡久久丫| 国产美女被遭强高潮免费一视频| 2021国产成人精品久久| 国产欧美综合在线观看第十页| 精品国产成人国产在线视| 欧美性XXXX极品HD欧美风情| 国产熟妇另类久久久久久| 人妻中文字幕精品系列| 四虎影视库国产精品一区| 国产精品99久久久久久董美香| 久久国内精品自在自线观看| 日夜啪啪一区二区三区| 日韩幕无线码一区中文| 欧美偷窥清纯综合图区| 日韩欧美视频一区二区三区| 夜夜添狠狠添高潮出水| 日日摸天天爽天天爽视频| 亚洲综合网中文字幕在线| 无码人妻aⅴ一区二区三区蜜桃| 久久精品国产99国产精品严洲| 激情久久综合精品久久人妻| 国产99视频精品免费视频76| 久久99久国产精品66| 绥江县| 国产在线中文字幕精品| 久久婷婷成人综合色综合| 亚洲AV毛片一区二区三区| 日本一级午夜福利免费区| 春色校园综合人妻av| 国产第一区二区三区精品| 亚洲av综合av一区| 亚洲а∨精品天堂在线| 一区二区三区四区亚洲自拍| 国产欧美精品区一区二区三区| 亚洲中文字幕久久精品码|