青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

羅朝輝(飄飄白云)

關(guān)注嵌入式操作系統(tǒng),移動(dòng)平臺(tái),圖形開發(fā)。-->加微博 ^_^

  C++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
  85 隨筆 :: 0 文章 :: 169 評(píng)論 :: 0 Trackbacks

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

來源:vcblog 作者:Stephan T. Lavavej 翻譯:飄飄白云  

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

簡(jiǎn)介

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

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


本文為 Part 2 的第一頁

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


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


C++ 98/03 中的 lvalues 和 rvalues

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


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


C++ 03 標(biāo)準(zhǔn) 3.10/1 節(jié)上說: “每一個(gè)表達(dá)式要么是一個(gè) lvalue ,要么就是一個(gè) rvalue 。” 應(yīng)該謹(jǐn)記 lvalue 跟 rvalue 是針對(duì)表達(dá)式而言的,而不是對(duì)象。


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


rvalue 是指那些表達(dá)式結(jié)束時(shí)(在分號(hào)處)就不復(fù)存在了的臨時(shí)對(duì)象。例如: 1729 , x + y , std::string("meow") , 和 x++ 都是 rvalue。


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


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


前面的例子不考慮操作符重載的情況,它只是普通的函數(shù)調(diào)用語義。“一個(gè)函數(shù)調(diào)用是一個(gè) lvalue 當(dāng)且僅當(dāng)它返回一個(gè)引用”(見 C++ 03 5.2.2/10)。因此,給定語句 vercor<int> v(10, 1729); , v[0] 是一個(gè) lvalue,因?yàn)椴僮鞣?[]() 返回 int& (且 &v[0] 是合法可用的); 而給定語句 string s("foo");和 string t("bar");,s + t 是一個(gè)rvalue,因?yàn)椴僮鞣?+() 返回 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 (可以用這個(gè)引用來讀取和修改原來的值),但不能綁定到 const lvalue,因?yàn)槟菍⑦`背 const 正確性;也不能把它綁定到非常量 rvalue,這樣做極端危險(xiǎn),你用這個(gè)引用來修改臨時(shí)對(duì)象,但臨時(shí)對(duì)象早就不存在了,這將導(dǎo)致難以捕捉而令人討厭的 bug,因此 C++ 明智地禁止這這么做。(我要補(bǔ)充一句:VC 有一個(gè)邪惡的擴(kuò)展允許這么蠻干,但如果你編譯的時(shí)候加上參數(shù) /W4 ,編譯器通常會(huì)提示警告"邪惡的擴(kuò)展被激活了”)。也不能把它綁定到 const ravlue,因?yàn)槟菚?huì)是雙倍的糟糕。(細(xì)心的讀者應(yīng)該注意到了我在這里并沒有談及模板參數(shù)推導(dǎo))。


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


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


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


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


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


拷貝的問題


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


最最沒有必要的拷貝是拷貝那些立馬會(huì)被銷毀的對(duì)象。你有過復(fù)印一份文件,并馬上把原件扔掉的經(jīng)歷么(假定原件和復(fù)件是相同的)?那簡(jiǎn)直是浪費(fèi),你應(yīng)該持有原件而不必費(fèi)勁去復(fù)印。下面是被我稱作“殺手級(jí)的示例”,來自標(biāo)準(zhǔn)委員會(huì)的例子(見提案 N1377),假設(shè)你有一大堆 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;


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

 

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

 

事實(shí)上,因?yàn)槊恳粋€(gè)串接操作都會(huì)拷貝所有已經(jīng)串接好的字符,所以那個(gè)復(fù)雜度是字符串長(zhǎng)度的平方了。哎呀!這太浪費(fèi)了!這點(diǎn)確實(shí)讓 C++ 尷尬。事情怎么會(huì)搞成這樣呢?有沒有改善的辦法?

 

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

 

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


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


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


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


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


ravlue 引用:初始化


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


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


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


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

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

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

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


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

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

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


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


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 是沒問題的;要領(lǐng)就是它們可以被用來修改臨時(shí)對(duì)象。


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


rvalue 引用:重載決議

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


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()

 

在實(shí)踐中,全部重載 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) 非常量表達(dá)式傾向于綁定到非常量引用上。


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


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


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


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


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


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


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


move 語義:模式


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


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.


這里有幾點(diǎn)值得注意:


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


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


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


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


·永遠(yuǎn)不會(huì)自動(dòng)生成 move 構(gòu)造函數(shù)和 move 賦值函數(shù)。

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

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

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


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


( 轉(zhuǎn)載時(shí)請(qǐng)注明作者和出處。未經(jīng)許可,請(qǐng)勿用于商業(yè)用途)
posted on 2009-06-01 20:40 羅朝輝 閱讀(2567) 評(píng)論(6)  編輯 收藏 引用 所屬分類: C/C++

評(píng)論

# re: 【譯】VC10中的C++0x特性 part 2(1):右值引用 2009-06-01 22:35 zhaoyg
也不知道C++0X與以前的標(biāo)準(zhǔn)有多大的不同。
by the way
謝謝博主的文章  回復(fù)  更多評(píng)論
  

# re: 【譯】VC10中的C++0x特性 part 2(1):右值引用 2009-06-01 23:11 飄飄白云
這一系列文章中提及的特性都是 C++0x 中新加的特性~~
C++0x 是增進(jìn)式改進(jìn),會(huì)向后和C++98兼容的~~
  回復(fù)  更多評(píng)論
  

# re: 【譯】VC10中的C++0x特性 part 2(1):右值引用 2009-06-01 23:57 陳包
我想問一下 怎么把c++的exe文件放到Vfp的項(xiàng)目里去啊 我做了一個(gè)表單 可以調(diào)用這個(gè)exe文件 但是我想把這倆個(gè)放到一起去 連編成一個(gè)可執(zhí)行文件 但是報(bào)錯(cuò)說那個(gè)不是vfp的exe文件 不知道怎么辦了 請(qǐng)教一下  回復(fù)  更多評(píng)論
  

# re: 【譯】VC10中的C++0x特性 part 2(1):右值引用 2009-06-02 08:55 飄飄白云
@陳包
但是我想把這倆個(gè)放到一起去 連編成一個(gè)可執(zhí)行文件

寫個(gè)dll吧,淺顯之見,對(duì)Vfp俺不熟悉~~  回復(fù)  更多評(píng)論
  

# re: 【譯】VC10中的C++0x特性 part 2(1):右值引用 2009-06-03 08:54
C++0x 標(biāo)準(zhǔn)發(fā)布了沒有?或啥時(shí)發(fā)布 ?  回復(fù)  更多評(píng)論
  

# re: 【譯】VC10中的C++0x特性 part 2(1):右值引用 2012-12-14 19:11 silverbullettt
非常感謝 LZ!  回復(fù)  更多評(píng)論
  

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            久久精品国产欧美激情| 亚洲欧美日本另类| 久久这里只有| 欧美电影免费观看高清| 999在线观看精品免费不卡网站| 久久免费高清视频| 亚洲欧美日韩在线| 亚洲欧美日韩国产另类专区| 一区二区三区久久网| 亚洲美洲欧洲综合国产一区| 亚洲欧洲在线一区| 蜜月aⅴ免费一区二区三区| 性欧美在线看片a免费观看| 午夜日韩激情| 欧美激情精品久久久久久久变态 | 亚洲精品免费在线| 亚洲视频精选| 午夜影院日韩| 欧美~级网站不卡| 国产精品视频xxxx| 黄色另类av| 在线午夜精品| 欧美成人免费va影院高清| 夜夜嗨av一区二区三区中文字幕 | 亚洲作爱视频| 性做久久久久久| 麻豆乱码国产一区二区三区| 麻豆精品在线播放| 亚洲欧美日本在线| 欧美高潮视频| 国产一区二区三区在线免费观看| 狠狠色狠色综合曰曰| 午夜精品久久久| 欧美成人在线免费视频| 一区二区三区偷拍| 欧美激情在线免费观看| 国产尤物精品| 中国日韩欧美久久久久久久久| 久久国产精品久久久久久| 亚洲三级电影在线观看 | 亚洲国产成人在线播放| 亚洲综合成人婷婷小说| 亚洲日本乱码在线观看| 久久99伊人| 在线播放亚洲一区| 午夜一区二区三区不卡视频| 国产精品扒开腿爽爽爽视频| 亚洲乱码国产乱码精品精可以看 | 欧美日韩综合精品| 国产专区综合网| 欧美成人嫩草网站| 亚洲精品免费一区二区三区| 久热成人在线视频| 麻豆精品网站| 亚洲大片av| 亚洲人在线视频| 亚洲美女福利视频网站| 欧美激情一区二区久久久| 久久9热精品视频| 亚洲欧美日韩一区二区三区在线观看 | 久久精品视频免费| 欧美一级免费视频| 在线播放视频一区| 一本色道久久综合狠狠躁的推荐| 国产精品萝li| 亚洲欧美伊人| 欧美激情视频给我| 久久久精品性| 欧美日韩一区在线观看视频| 欧美日韩岛国| 欧美www视频| 国产精品欧美久久| 亚洲激情另类| 亚洲二区免费| 欧美一区二区视频97| 久久黄金**| 亚洲视频电影图片偷拍一区| 欧美一级艳片视频免费观看| 夜夜嗨av一区二区三区中文字幕| 性欧美xxxx视频在线观看| 国产精品高潮呻吟久久av无限 | 欧美色视频一区| 免费不卡在线观看| 国产老肥熟一区二区三区| 亚洲高清av在线| 激情欧美丁香| 久久九九电影| 激情久久一区| 午夜精品久久久99热福利| 亚洲国产精品美女| 美女精品自拍一二三四| 久久久久国产精品一区二区| 欧美涩涩网站| 亚洲一二三四久久| 亚洲欧美另类国产| 国产精品女主播| 欧美在线视频二区| 欧美国产视频在线观看| 欧美在线啊v| 亚洲视频电影在线| 欧美日韩国产高清视频| 性欧美1819sex性高清| 欧美激情亚洲激情| 亚洲激情成人网| 欧美精品久久一区| 亚洲天堂偷拍| 久久婷婷久久| aa级大片欧美三级| 国产精品永久入口久久久| 久久精品欧美| 亚洲毛片在线看| 欧美v亚洲v综合ⅴ国产v| 亚洲精品免费在线观看| 一本色道精品久久一区二区三区 | 国产精品揄拍一区二区| 亚洲一区二区三区精品视频| 欧美国产日韩亚洲一区| 亚洲欧美日韩在线高清直播| 国内精品福利| 国产精品视频xxx| 国产精品99久久久久久有的能看| 国产精品香蕉在线观看| 亚洲人成网站999久久久综合| 欧美日韩国产专区| 亚洲人成精品久久久久| 久久久免费精品| 欧美一区二区三区啪啪| 亚洲午夜视频在线观看| 亚洲精品偷拍| 亚洲免费激情| 亚洲精品视频一区二区三区| 伊人蜜桃色噜噜激情综合| 国产丝袜美腿一区二区三区| 国产欧美一区二区精品性| 国产日本欧洲亚洲| 欧美日韩在线免费视频| 亚洲黄色在线看| 欧美成人精品在线视频| 嫩草影视亚洲| 亚洲国产婷婷综合在线精品| 欧美激情一区二区三区全黄| 欧美承认网站| 亚洲精品久久久蜜桃| 一区二区三区国产| 欧美亚洲一级片| 欧美成人激情视频| 午夜精品久久久久久99热软件| 亚洲欧美日韩国产另类专区| 久久精品国产一区二区三| 欧美精品v日韩精品v国产精品| 玖玖玖国产精品| 欧美色区777第一页| 亚洲在线电影| 亚洲欧美精品suv| 久久中文字幕一区| 国产精品国产三级国产a| 韩国v欧美v日本v亚洲v| 亚洲午夜在线视频| 亚洲国产黄色| 久久婷婷久久一区二区三区| 国产精品劲爆视频| 一区二区欧美日韩视频| 欧美与欧洲交xxxx免费观看 | 国产精品国产三级国产普通话三级 | 欧美久久影院| 亚洲精品日韩在线观看| 亚洲第一区在线| 欧美a级片网| 99精品视频一区| 亚洲精品国产精品国自产在线 | 久久婷婷久久一区二区三区| 久久婷婷国产综合精品青草| 亚洲人成艺术| 亚洲欧美国产日韩天堂区| 影音先锋久久| 日韩一二三在线视频播| 欧美日韩亚洲精品内裤| 久久久久女教师免费一区| 免费视频久久| 久久精品视频导航| 欧美日韩中文字幕日韩欧美| 久久精品在线观看| 欧美日韩亚洲一区二区三区在线观看| 亚洲免费视频观看| 亚洲精品国产精品久久清纯直播| 欧美sm极限捆绑bd| 一片黄亚洲嫩模| aa级大片欧美| 国产精品腿扒开做爽爽爽挤奶网站| 欧美在线不卡视频| 久久久久久久网| 狠狠网亚洲精品| 亚洲欧美激情在线视频| 欧美日韩在线一区| 最新国产成人av网站网址麻豆| 久久久精品性| 亚洲欧美日韩中文在线制服| 最新成人在线| 欧美日韩激情小视频| 一本久久青青|