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

woaidongmao

文章均收錄自他人博客,但不喜標(biāo)題前加-[轉(zhuǎn)貼],因其丑陋,見(jiàn)諒!~
隨筆 - 1469, 文章 - 0, 評(píng)論 - 661, 引用 - 0
數(shù)據(jù)加載中……

泛型編程-轉(zhuǎn)移構(gòu)造函數(shù)(Generic Programming: Move Constructor)

 

http://www.vckbase.com/document/viewdoc/?id=847

編譯:死貓
校對(duì):Wang Tianxing

1 引言

我相信大家很了解,創(chuàng)建、復(fù)制和銷(xiāo)毀臨時(shí)對(duì)象是C++編譯器最?lèi)?ài)的戶(hù)內(nèi)運(yùn)動(dòng)。不幸的是,這些行為會(huì)降低C++程序的性能。確實(shí),臨時(shí)對(duì)象通常被視為C++程序低效的第一因素[1]

下面的代碼是正確的:

vector < string > ReadFile();
vector < string > vec = ReadFile();

或者

string s1, s2, s3;
//...
s1 = s2 + s3;

但是,如果關(guān)心效率,則需要限制類(lèi)似代碼的使用。ReadFile()operator+創(chuàng)建的臨時(shí)對(duì)象分別被復(fù)制然后再?gòu)U棄。這是一種浪費(fèi)!

為了解決這個(gè)問(wèn)題,需要一些不太優(yōu)雅的約定。例如,可以按照引用傳遞函數(shù)參數(shù):

void ReadFile(vector < string > & dest);
vector < string > dest;
ReadFile(dest);

這相當(dāng)令人討厭。更糟的是,運(yùn)算符沒(méi)有這個(gè)選擇,所以如果想高效的處理大對(duì)象,程序員必須限制創(chuàng)建臨時(shí)對(duì)象的運(yùn)算符的使用:

string s1, s2, s3;
//...
s1 = s2;
s1 += s3;

這種難纏的手法通常減緩了設(shè)計(jì)大程序的大團(tuán)隊(duì)的工作效率,這種強(qiáng)加的持續(xù)不斷的煩惱扼殺了編寫(xiě)代碼的樂(lè)趣而且增加了代碼數(shù)量。難道從函數(shù)返回值,使用運(yùn)算符傳遞臨時(shí)對(duì)象,這樣做是錯(cuò)誤的嗎?

一個(gè)正式的基于語(yǔ)言的解決方案的提議已經(jīng)遞交給了標(biāo)準(zhǔn)化委員會(huì)[2]Usenet上早已引發(fā)了大討論,本文也因此在其中被反復(fù)討論過(guò)了。

本文展示了如何解決C++存在的不必要的復(fù)制問(wèn)題的方法。沒(méi)有百分之百讓人滿(mǎn)意地解決方案,但是一個(gè)干凈的程度是可以達(dá)到的。讓我們一步一步的來(lái)創(chuàng)建一個(gè)強(qiáng)有力的框架,來(lái)幫助我們從程序中消除不需要的臨時(shí)對(duì)象的復(fù)制。這個(gè)解決方案不是百分之百透明的,但是它消除了所有的不需要的復(fù)制,而且封裝后足以提供一個(gè)可靠的替代品,直到多年以后,一個(gè)干凈的、基于語(yǔ)言的標(biāo)準(zhǔn)化的實(shí)現(xiàn)出現(xiàn)。

2 臨時(shí)對(duì)象和轉(zhuǎn)移構(gòu)造函數(shù)”(Move Constructor)

在和臨時(shí)對(duì)象斗爭(zhēng)了一段時(shí)間之后,我們意識(shí)到在大多數(shù)情況下,完全消除臨時(shí)對(duì)象是不切實(shí)際的。大多數(shù)時(shí)候,關(guān)鍵是消除臨時(shí)對(duì)象的復(fù)制而不是臨時(shí)對(duì)象本身。下面詳細(xì)的討論一下這個(gè)問(wèn)題。

大多數(shù)具有昂貴的復(fù)制開(kāi)銷(xiāo)的數(shù)據(jù)結(jié)構(gòu)將它們的數(shù)據(jù)以指針或者句柄的形式儲(chǔ)存。典型的例子包括,字符串(String)類(lèi)型儲(chǔ)存大小(size)和字符指針(char*),矩陣(Matrix)類(lèi)型儲(chǔ)存一組整數(shù)維數(shù)和數(shù)據(jù)存儲(chǔ)區(qū)指針(double*),文件(File)類(lèi)型儲(chǔ)存一個(gè)文件句柄(handle)

如你所見(jiàn),復(fù)制字符串、矩陣或者文件的開(kāi)銷(xiāo)不是來(lái)自于復(fù)制實(shí)際的數(shù)據(jù)成員,而是來(lái)自于指針或者句柄指向的數(shù)據(jù)的復(fù)制。

因此,對(duì)于消除復(fù)制的目的來(lái)說(shuō),檢測(cè)臨時(shí)對(duì)象是一個(gè)好方法。殘酷點(diǎn)說(shuō)就是,既然一個(gè)對(duì)象死定了,我們完全可以趁著它還新鮮,把它用作器官捐獻(xiàn)者。

順便說(shuō)一下什么是臨時(shí)對(duì)象?這里給出一個(gè)非正式的定義:

當(dāng)且僅當(dāng)離開(kāi)一段上下文(context)時(shí)在對(duì)象上執(zhí)行的僅有的操作是析構(gòu)函數(shù)時(shí),一個(gè)對(duì)象被看成是臨時(shí)的。這里上下文可能是一個(gè)表達(dá)式,也可能是一個(gè)語(yǔ)句范圍,例如函數(shù)體。

C++標(biāo)準(zhǔn)沒(méi)有定義臨時(shí)對(duì)象,但是它假定臨時(shí)對(duì)象是匿名的,例如函數(shù)的返回值。按照我們的更一般化的定義,在函數(shù)中定義的命名的棧分配的變量也是臨時(shí)的。稍后為了便于討論我們使用這個(gè)一般化的定義。

考慮這個(gè)String類(lèi)的實(shí)現(xiàn)(僅作為示例)

class String
{
       char* data_;
       size_t length_;
public:
       ~String()
       {
              delete[] data_;
       }
       String(const String& rhs)
              : data_(new char[rhs.length_]), length_(rhs.length_)
       {
              std::copy(rhs.data_, rhs.data_ + length_, data_);
       }
       String& operator=(const String&);
       //...
};

這里復(fù)制的成本主要由data_的復(fù)制組成,也就是分配新的內(nèi)存并復(fù)制。如果可以探測(cè)到rhs實(shí)際上是臨時(shí)的就好了。考慮下面的C++偽代碼:

class String
{
       //...同前...
       String(temporary String& rhs)
              : data_(rhs.data_), length_(rhs.length_)
       {
              //復(fù)位源字符串使它可以被銷(xiāo)毀
              //因?yàn)榕R時(shí)對(duì)象的析構(gòu)函數(shù)仍然要執(zhí)行
              rhs.data_ =0;
       }
       //...
}

這個(gè)我們虛構(gòu)的重載構(gòu)造函數(shù)String(temporary String&)在創(chuàng)建一個(gè)String臨時(shí)對(duì)象(按照前面的定義)時(shí)調(diào)用。然后,這個(gè)構(gòu)造函數(shù)執(zhí)行了一個(gè)rhs對(duì)象轉(zhuǎn)移的構(gòu)造過(guò)程,只是簡(jiǎn)單的復(fù)制指針而不是復(fù)制指針指向的內(nèi)存塊。最后,轉(zhuǎn)移構(gòu)造函數(shù)復(fù)位源指針rhs.data_(恢復(fù)為空指針)。使用這個(gè)方法,當(dāng)臨時(shí)對(duì)象被銷(xiāo)毀時(shí),delete[]會(huì)無(wú)害的應(yīng)用在空指針上[譯注:C++保證刪除空指針是安全的]

一個(gè)重要的細(xì)節(jié)是轉(zhuǎn)移構(gòu)造rhs.length_沒(méi)有被清0。按照教條主義的觀(guān)點(diǎn),這是不正確的,因?yàn)?span lang="EN-US">data_==0length_!=0,所以字符串被破壞了。但是,這里有一個(gè)很站得住腳的理由,因?yàn)?span lang="EN-US">rhs的狀態(tài)沒(méi)有必要是完整的,只要它可以被安全而正確的銷(xiāo)毀就行了。這是因?yàn)闀?huì)被應(yīng)用在rhs上唯一一個(gè)操作就是析構(gòu)函數(shù),而不是其他的。所以只要rhs可以被安全的銷(xiāo)毀,而不用去看是否像一個(gè)合法的字符串。

轉(zhuǎn)移構(gòu)造函數(shù)對(duì)于消除不需要的臨時(shí)對(duì)象復(fù)制是一個(gè)良好的解決方案。我們只有一個(gè)小問(wèn)題,C++語(yǔ)言中沒(méi)有temporary關(guān)鍵字。

還應(yīng)該注意到臨時(shí)對(duì)象的探測(cè)不會(huì)幫助所有的類(lèi)。有時(shí),所有的數(shù)據(jù)直接存儲(chǔ)在容器中。考慮:

class FixedMatrix
{
       double data_[256][256];
public:
       //...操作...
};

對(duì)這樣一個(gè)類(lèi),實(shí)際上復(fù)制成本在于逐字節(jié)的復(fù)制sizeof(FixedMatrix)個(gè)字節(jié),而探測(cè)臨時(shí)對(duì)象并沒(méi)有幫助[譯注:因?yàn)閿?shù)組不是指針,不能直接交換地址]

3 過(guò)去的解決方案

不必要的復(fù)制是C++社區(qū)長(zhǎng)期存在的問(wèn)題。有兩個(gè)努力方向齊頭并進(jìn),其一是從編碼和庫(kù)編寫(xiě)的角度,另一個(gè)是語(yǔ)言定義和編譯器編寫(xiě)層面。

語(yǔ)言/編譯器觀(guān)點(diǎn)方面,有返回值優(yōu)化(Return Value Optimization, RVO)RVOC++語(yǔ)言定義所允許[3][譯注:但是不是強(qiáng)制性的,而是實(shí)現(xiàn)定義的]。基本上,編譯器假定通過(guò)拷貝構(gòu)造函數(shù)(Copy Constructor)復(fù)制返回值。

確切地說(shuō),基于這樣的假定,因此編譯器可以消除不必要的復(fù)制。例如,考慮:

vector< String > ReadFile()
{
       vector< String > result;
       //...填充result...
       return result;
}
vector< String > vec=ReadFile();

聰明的編譯器可以將vec的地址作為一個(gè)隱藏的參數(shù)傳遞給ReadFile而把result創(chuàng)建在那個(gè)地址上。所以上面的源代碼生成的代碼看起來(lái)像這樣:

void ReadFile(void* __dest)
{
       //使用placement newdest地址創(chuàng)建vector
       vector< String >& result=
              *new(__dest) vector< String >;
       //...填充result...
}
 
 
//假設(shè)有合適的字節(jié)對(duì)齊
char __buf[sizeof(vector< String >)];
ReadFile(__buf);
vector< String >& vec=
       *reinterpret_cast < vector< String >* >(__buf);

RVO有不同的風(fēng)格,但要旨是相同的:編譯器消除了一次拷貝構(gòu)造函數(shù)的調(diào)用,通過(guò)簡(jiǎn)單的在最終目的地上構(gòu)造函數(shù)返回值。

不幸的是,RVO的實(shí)現(xiàn)不像看上那樣容易。考慮ReadFile稍稍修改后的版本:

vector< String > ReadFile()
{
       if (error) return vector< String >();
       if (anotherError)
       {
              vector< String > dumb;
              dumb.push_back("This file is in error.");
              return dumb;
       }
       vector< String > result;
       //...填充result...
       return result;
}

******************************************************

Wang Tianxing校注:

這個(gè)例子并不是很有說(shuō)服力。里面的三個(gè)對(duì)象的作用域互不相交,因此還是比較容易使用 RVO 的。難以運(yùn)用RVO的是這種情況:

vector< String > ReadFile()
{
       vector< String > dumb;
       dumb.push_back( "This file is in error." );
 
 
       vector< String > result;
       // ... 填充 result ...
 
 
       return error ? dumb : result;
}

******************************************************

現(xiàn)在有不止一個(gè)局部變量需要被映射到最后的結(jié)果上,他們有好幾個(gè)。有些是命名的(dumb/result),而另一些是無(wú)名的臨時(shí)對(duì)象。無(wú)需多說(shuō),面對(duì)這樣的局面,大量?jī)?yōu)化器會(huì)投降并且服從保守的和缺乏效率的方法。

即使想寫(xiě)不導(dǎo)致混淆RVO實(shí)現(xiàn)的直線(xiàn)條的代碼,也會(huì)因?yàn)槁?tīng)到每個(gè)編譯器或者編譯器版本都有自己探測(cè)和應(yīng)用RVO的規(guī)則而失望。一些RVO應(yīng)用僅僅針對(duì)返回?zé)o名臨時(shí)對(duì)象的函數(shù),這是最簡(jiǎn)單的RVO形式。最復(fù)雜的RVO應(yīng)用之一是函數(shù)返回值是一個(gè)命名的結(jié)果,叫做命名返回值優(yōu)化(Named RVONRVO)

本質(zhì)上,寫(xiě)程序時(shí)要指望可移植的RVO,就要依賴(lài)于你的代碼的精確寫(xiě)法(在很難定義的精確意義下),依賴(lài)于月亮的圓缺,依賴(lài)于你的鞋的尺碼。

但是,別忙,還有很多種情況下RVO無(wú)法避免臨時(shí)對(duì)象的拷貝。編譯器時(shí)常不能應(yīng)用RVO,即使它很想。考慮稍稍改變后的 ReadFile() 的調(diào)用:

vector vec;
vec=ReadFile();

這個(gè)改變看上去完全沒(méi)有惡意,但是卻導(dǎo)致了巨大的差異。現(xiàn)在不再調(diào)用拷貝構(gòu)造函數(shù)而調(diào)用賦值運(yùn)算符(assignment operator),這是令一個(gè)不同的脫韁野馬。除非編譯器優(yōu)化技巧完全像是在使用魔法,現(xiàn)在真的可以和RVO吻別了:vector<T>::operator=(const vector<T>&)期望一個(gè)vector的常量引用,所以ReadFile會(huì)返回一個(gè)臨時(shí)對(duì)象,綁定到一個(gè)常量引用,復(fù)制到vec,然后被廢棄。不必要的臨時(shí)對(duì)象又來(lái)了!

在編碼方面,一個(gè)長(zhǎng)期被推薦的技術(shù)是COW(按需復(fù)制,copy-on-write)[4],這是一個(gè)基于引用計(jì)數(shù)的技巧。

COW有幾個(gè)優(yōu)點(diǎn),其中之一是探測(cè)和消除了不必要的復(fù)制。例如,函數(shù)返回時(shí),返回的對(duì)象的引用計(jì)數(shù)是1。然后復(fù)制的時(shí)候,引用計(jì)數(shù)增加到2。最后,銷(xiāo)毀臨時(shí)對(duì)象的時(shí)候,引用計(jì)數(shù)回到1,引用指向的目的地僅僅是數(shù)據(jù)的所有者。實(shí)際上沒(méi)有復(fù)制動(dòng)作發(fā)生。

不幸的是,引用計(jì)數(shù)在多線(xiàn)程安全性方面有大量的缺陷,增加自己的開(kāi)銷(xiāo)和大量隱藏的陷阱[4]COW是如此之笨拙,因此,雖然它有很多優(yōu)點(diǎn),最近的STL實(shí)現(xiàn)都沒(méi)有為std::string使用引用計(jì)數(shù),盡管實(shí)際上std::string的接口有目的設(shè)計(jì)為支持引用計(jì)數(shù)!

已經(jīng)開(kāi)發(fā)了幾個(gè)實(shí)現(xiàn)不可復(fù)制對(duì)象的辦法,auto_ptr是最精煉的一個(gè)。auto_ptr是容易正確使用的,但是不幸的是,剛好也容易不正確的使用。本文的討論的解決方法擴(kuò)充了定義auto_ptr中使用的技術(shù)。

4 Mojo

Mojo(聯(lián)合對(duì)象轉(zhuǎn)移,Move of Joint Objects)是一項(xiàng)編碼技術(shù),又是一個(gè)消除不必要的臨時(shí)對(duì)象復(fù)制的小框架。Mojo通過(guò)辨別臨時(shí)對(duì)象和合法的非臨時(shí)的對(duì)象而得以工作。

4.1 傳遞函數(shù)參數(shù)

Mojo引發(fā)了一個(gè)有趣的分析,即函數(shù)參數(shù)傳遞約定的調(diào)查。Mojo之前的一般建議是:

[規(guī)則1]如果函數(shù)試圖改變參數(shù)(也就是作為副作用),則把參數(shù)作為非常量對(duì)象的指針或者引用傳遞。例如:

void Transmogrify(Widget& toChange);
void Increment(int* pToBump);

[規(guī)則2]如果函數(shù)不修改它的參數(shù)而且參數(shù)是基本數(shù)據(jù)類(lèi)型,則按照值傳遞參數(shù)。例如:

double Cube(double value);

[規(guī)則3]否則,參數(shù)是用戶(hù)自定義類(lèi)型(或者模板的類(lèi)型參數(shù))而且一定不變,則作為常量引用傳遞參數(shù)。例如:

String& String::operator=(const String& rhs);
template< class T > vector< T >::push_back(const T&);

第三條規(guī)則試圖避免意外的大對(duì)象的復(fù)制。然而,有時(shí)第三條規(guī)則強(qiáng)制不必要的復(fù)制進(jìn)行而不是阻止它的發(fā)生。考慮下面的Connect函數(shù):

void Canonicalize(String& url);
void ResolveRedirections(String& url);
 
 
void Connect(const String& url)
{
       String finalUrl=url;
       Canonicalize(finalUrl);
       ResolveRedirections(finalUrl);
       //...使用finalUrl...
}

Connect函數(shù)獲得一個(gè)常量引用的參數(shù),并快速的創(chuàng)建一個(gè)副本。然后進(jìn)一步處理副本。

這個(gè)函數(shù)展示了一個(gè)影響效率的常量引用的參數(shù)使用。Connect的函數(shù)聲明暗示了:我不需要一個(gè)副本,一個(gè)常量引用就足夠了,而函數(shù)體實(shí)際上卻創(chuàng)建了一個(gè)副本。所以假如現(xiàn)在這樣寫(xiě):

String MakeUrl();
//...
Connect(MakeUrl());

可以預(yù)料MakeUrl()會(huì)返回一個(gè)臨時(shí)對(duì)象,他將被復(fù)制然后銷(xiāo)毀,也就是令人畏懼的不需要的復(fù)制模式。對(duì)一個(gè)優(yōu)化復(fù)制的編譯器來(lái)說(shuō),不得不作非常困難的工作,其一是訪(fǎng)問(wèn)Connect函數(shù)的定義(這對(duì)于分離編譯模塊來(lái)說(shuō)很困難),其二是解析Connect函數(shù)的定義并進(jìn)一步理解它,其三是改變Connect函數(shù)的行為以使臨時(shí)對(duì)象和finalUrl融合。

假如現(xiàn)在將Connect函數(shù)改寫(xiě)如下:

void Connect(String url)   //注意按值傳遞
{
       Canonicalize(url);
       ResolveRedirections(url);
       //... 使用 url ...
}

Connect的調(diào)用者的觀(guān)點(diǎn)來(lái)看,絕對(duì)沒(méi)有什么區(qū)別:雖然改變了語(yǔ)法接口,但是語(yǔ)義接口仍然是相同的。對(duì)編譯器來(lái)說(shuō),語(yǔ)法的改變使所有事物都發(fā)生了改變。現(xiàn)在編譯器有更多的余地關(guān)心url臨時(shí)對(duì)象了。例如,在上面提到的例子中:

Connect(MakeUrl());

編譯器不一定要真的聰明到將MakeUrl返回的臨時(shí)對(duì)象和Connect函數(shù)需要的常量融合。如果那么做,確實(shí)會(huì)更加困難。最終,MakeUrl的真正結(jié)果會(huì)被改變而且在Connect函數(shù)中使用。使用常量引用參數(shù)的版本會(huì)使編譯器窒息,阻止它實(shí)行任何優(yōu)化,而使用傳值參數(shù)的版本和編譯器順暢的合作。

這個(gè)新版本的不利之處在于,現(xiàn)在調(diào)用Connect也許生成了更多的機(jī)器碼。考慮:

String someUrl=...;
Connect(someUrl);

在這種情況下,第一個(gè)版本簡(jiǎn)單的傳遞someUrl的引用[譯注:從非常量到常量是標(biāo)準(zhǔn)轉(zhuǎn)型]。第二個(gè)版本會(huì)創(chuàng)建一個(gè)someUrl的副本,調(diào)用Connect,然后銷(xiāo)毀那個(gè)副本。隨著調(diào)用Connect的靜態(tài)數(shù)量的增長(zhǎng),代碼大小的開(kāi)銷(xiāo)同時(shí)增長(zhǎng)。另一方面,例如Connect(MakeUrl())這樣的調(diào)用會(huì)引入臨時(shí)對(duì)象,在第二個(gè)版本中又剛好生成更少的代碼。在多數(shù)情況下,大小差異好像不會(huì)導(dǎo)致問(wèn)題產(chǎn)生[譯注:在某些小內(nèi)存應(yīng)用中則是一個(gè)問(wèn)題,例如嵌入式應(yīng)用環(huán)境]

所以我們給出了一套不同的推薦規(guī)則:

  • [規(guī)則1]如果函數(shù)內(nèi)部總是制作參數(shù)的副本,按值傳遞。
  • [規(guī)則2]如果函數(shù)從來(lái)不復(fù)制參數(shù),按常量引用傳遞。
  • [規(guī)則3]如果函數(shù)有時(shí)復(fù)制參數(shù),而且關(guān)心效率,則按照Mojo協(xié)議。

現(xiàn)在只留下開(kāi)發(fā)Mojo協(xié)議了,不管它是什么。

主要的想法是重載同樣的函數(shù)(例如Connect),目的是辨別臨時(shí)的和非臨時(shí)的值。后者也稱(chēng)為左值(lvalue),因?yàn)闅v史原因,左值因?yàn)榭梢猿霈F(xiàn)在賦值運(yùn)算符的左邊而得名。

現(xiàn)在開(kāi)始重載Connect,第一個(gè)想法是定義Connect(const String&)來(lái)捕捉常量對(duì)象。然而這是錯(cuò)誤的,因?yàn)檫@個(gè)聲明吞吃了所有的String對(duì)象,不管是左值(lvalue)或者臨時(shí)對(duì)象[譯注:前面提到過(guò),非常量可以隱式轉(zhuǎn)型為常量,這是標(biāo)準(zhǔn)轉(zhuǎn)型動(dòng)作]。所以第一個(gè)好主意是不要聲明接受常量引用的參數(shù),因?yàn)樗褚粋€(gè)黑洞一樣,吞噬所有的對(duì)象。

第二個(gè)嘗試是定義Connect(String&)試圖捕獲非常量的左值。這工作良好,特別是常量值和無(wú)名的臨時(shí)對(duì)象不能被這個(gè)重載版本接受,這是一個(gè)好的起點(diǎn)。現(xiàn)在我們只剩下在常量對(duì)象和非常量臨時(shí)對(duì)象之間作出區(qū)分了。

為了達(dá)到這個(gè)目的,我們采取了一種技術(shù),定義兩個(gè)替身類(lèi)型[譯注:原文是type sugar,嘿嘿,如果你愿意,可以叫他類(lèi)型砂糖,如果你喜歡吃糖的話(huà)。]ConstantStringTemporaryString,并且定義了從String對(duì)象到這些對(duì)象轉(zhuǎn)型運(yùn)算符:

class String;
 
 
//常量String的替身類(lèi)型
struct ConstantString
{
       const String* obj_;
};
 
 
//臨時(shí)String的替身類(lèi)型
struct TemporaryString : public ConstantString {};
 
 
class String
{
public:
       //...構(gòu)造函數(shù),析構(gòu)函數(shù),運(yùn)算符,等等......
       operator ConstantString() const
       {
              ConstantString result;
              result.obj_ = this;
              return result;
       }
       operator TemporaryString()
       {
              TemporaryString result;
              result.obj_ = this;
              return result;
       }
};

現(xiàn)在定義下面三個(gè)重載版本:

//綁定非常量臨時(shí)對(duì)象
void Connect(TemporaryString);
//綁定所有的常量對(duì)象(左值和臨時(shí)對(duì)象)
void Connect(ConstantString);
//綁定非常量左值
void Connect(String& str)
{
       //調(diào)用另一個(gè)重載版本
       Connect(ConstantString(str));
}

常量String對(duì)象被Connect(ConstantString)吸收。沒(méi)有其他綁定可以工作,另兩個(gè)僅僅被非常量String對(duì)象調(diào)用。

臨時(shí)對(duì)象不能調(diào)用Connect(String&)。然而它們可以調(diào)用Connect(TemporaryString)或者Connect(ConstantString),前者必然被選中而不發(fā)生歧義。原因是因?yàn)?span lang="EN-US">TemporaryStringConstantString派生而來(lái),一個(gè)應(yīng)該注意的詭計(jì)。

考慮一下ConstantStringTemporaryString都是獨(dú)立的類(lèi)型。那么,當(dāng)要求復(fù)制一個(gè)臨時(shí)對(duì)象時(shí),編譯器將同等的對(duì)待operator TemporaryY()/Y(TemporarY)或者operator ConstantY() const/Y(ConstantY)

為什么是同等的?因?yàn)榫瓦x擇成員函數(shù)來(lái)說(shuō),非常量到常量轉(zhuǎn)型是無(wú)摩擦的

因而,需要告訴編譯器更多的選擇第一個(gè)而不是第二個(gè)。那就是繼承在這里的作用。現(xiàn)在編譯器說(shuō):好吧,我猜我要經(jīng)過(guò)ConstantString或者TemporaryString...,但是等等,派生類(lèi)TemporaryString是更好的匹配!

這里的規(guī)則是從重載候選中選擇函數(shù)時(shí),匹配的派生類(lèi)被視作比匹配的基類(lèi)更好。

[譯注]

我對(duì)上述代碼稍作修改,從std::string派生了String,并在此基礎(chǔ)上按照Mojo的方式修改,結(jié)果在gcc3.2編譯器下的確如作者指出的行為一般無(wú)二。這條重載的決議規(guī)則很少在C++書(shū)籍中提到,Wang Tianxing從煙波浩淼的標(biāo)準(zhǔn)文本中找出了這條規(guī)則:

13.3.3.2 Ranking implicit conversion sequences [over.rank]
 
 
4 [...]
-- If class B is derived directly or indirectly
   from class A and class C is derived directly
   or indirectly from B,
  [...]
-- binding of an expression of type C to a
     object of type B is better than binding
     an expression of type C to a object
     of object A,

上面這些標(biāo)準(zhǔn)中的條款,是從隱式轉(zhuǎn)型的轉(zhuǎn)換等級(jí)中節(jié)選出來(lái)的,大致的意思是說(shuō),如果C繼承B,而B繼承A,那么類(lèi)型為C的表達(dá)式綁定到B的對(duì)象比到A的對(duì)象更好,這是上面敘述的技術(shù)的標(biāo)準(zhǔn)依據(jù)。此外,類(lèi)似的引用和指針的綁定也適用于此規(guī)則,這里省略了這些條款。

最后一個(gè)有趣的花樣是,繼承不需要必須是public的。存取規(guī)則和重載規(guī)則是不沖突的。

讓我們看看Connect如何工作的例子:

String s1("http://moderncppdesign.com");
// 調(diào)用Connect(String&)
Connect(s1);
// 調(diào)用operator TemporaryString()
// 接下來(lái)調(diào)用Connect(TemporaryString)
Conncet(String("http://moderncppdesign.com"));
const String s4("http://moderncppdesign.com");
// 調(diào)用operator ConstantString() const
// 接下來(lái)調(diào)用Connect(ConstantString)
Connect(s4);

如你所見(jiàn),我們達(dá)到了期望的主要目標(biāo):在臨時(shí)對(duì)象和所有其他對(duì)象之間制造了差別。這就是Mojo的要旨。

還有一些不太顯眼的問(wèn)題,大多數(shù)我們要一一解決。

首先是減少代碼重復(fù):Connect(String&)Connect(ConstantString)基本上作相同的事情。上面的代碼通過(guò)第一個(gè)重載函數(shù)調(diào)用第二個(gè)重載函數(shù)解決了這個(gè)問(wèn)題。

讓我們面對(duì)第二個(gè)問(wèn)題,為每個(gè)需要mojo的類(lèi)型寫(xiě)兩個(gè)小類(lèi)聽(tīng)上去不是很吸引人,所以讓我們開(kāi)始制作一些更具一般性的東西更便于使用。我們定義了一個(gè)mojo名字空間,并放入兩個(gè)泛型的ConstantTemporary類(lèi):

namespace mojo
{
       template < class T >
       class constant
       {
              const T* data_;
       public:
              explicit constant(const T& obj) : data_(&obj)
              {
              }
              const T& get() const
              {
                    return *data_;
              }
       };
       
       template < class T >
       class temporary : private constant< T >
       {
       public:
              explicit temporary(T& obj) : contant< T >( obj)
              {
              }
              T& get() const
              {
                    return const_cast< T& >(constant< T >::get());
              }
       };
}

讓我們?cè)俣x一個(gè)基類(lèi)mojo::enabled,它包括了兩個(gè)運(yùn)算符:

template < class T > struct enabled //mojo名字空間中
{
       operator temporary< T >()
       {
              return temporary< T >(static_cast< T& >(*this));
       }
       operator constant< T >() const
       {
              return constant< T >(static_cast< const T& >(*this));
       }
protected:
       enabled() {} //只能被派生
       ~enabled() {} //只能被派生
};

使用這個(gè)腳手架,將一個(gè)類(lèi)“mojo的任務(wù)可以想象會(huì)變得更簡(jiǎn)單:

class String : public mojo::enabled< String >
{
       //...構(gòu)造函數(shù),析構(gòu)函數(shù),運(yùn)算符,等等...
public:
       String(mojo::temporary< String > tmp)
       {
              String& rhs = tmp.get();
              //...執(zhí)行rhs*this的析構(gòu)性復(fù)制...
       }
};

這就是傳遞函數(shù)參數(shù)的Mojo協(xié)議。

通常,一切工作良好,你得到了一個(gè)好的設(shè)計(jì)品。不錯(cuò),那些意外的情況都控制在一個(gè)很小的范圍內(nèi),這使他們更有價(jià)值。

Mojo設(shè)計(jì)我們可以很容易檢測(cè)到一個(gè)類(lèi)是否支持Mojo。只需要簡(jiǎn)單的寫(xiě):

namespace mojo
{
       template < class T >
       struct traits
       {
              enum
              {
                    enabled = Loki::SuperSubclassStrict< enabled< T >, T >::value
              };
       };
};

Loki提供了探測(cè)一個(gè)類(lèi)型是否從另一個(gè)類(lèi)派生的機(jī)制。[5]

現(xiàn)在可以發(fā)現(xiàn)一個(gè)任意的類(lèi)型X是按照Mojo協(xié)議設(shè)計(jì)的,只要通過(guò)mojo::traits<X>::enabled即可確定。這個(gè)檢測(cè)機(jī)制對(duì)泛型編程是很重要的,很快我們就會(huì)看到它的作用。

4.2 函數(shù)返回值優(yōu)化

現(xiàn)在我們可以正確的傳遞參數(shù),讓我們看看如何將Mojo擴(kuò)展到函數(shù)返回值優(yōu)化。這次的目的又是具有可移植性的效率改善,即100%的消除不需要的復(fù)制而不依賴(lài)于特定的返回值優(yōu)化(RVO)實(shí)現(xiàn)。

讓我們先看看通常的建議怎么說(shuō)。出于好意,一些作者也推薦返回值的使用規(guī)則[7]

  • [規(guī)則4]當(dāng)函數(shù)返回用戶(hù)定義的對(duì)象的值的時(shí)候,返回一個(gè)常量值。例如:
const String operator+(const String& lhs,const String& rhs);

規(guī)則4的潛臺(tái)詞是使用戶(hù)定義的運(yùn)算符更加接近于內(nèi)建的運(yùn)算符可以禁止錯(cuò)誤的表達(dá)式的功能,就好像想是if (s1+s2==s3)的時(shí)候筆誤成了if (s1+s2=s3)。如果operator+返回一個(gè)常量值,這個(gè)特定的BUG將會(huì)在編譯期間被檢測(cè)到[譯注:返回內(nèi)建數(shù)據(jù)類(lèi)型的值隱含地總是常量的,而用戶(hù)定義類(lèi)型則需要顯式的用常量限定符指出]。然而,其他的作者[6]推薦不要返回常量值。

冷靜的看,任何返回值都是短暫的,它是剛剛被創(chuàng)建就要很快消失的短命鬼。那么,為什么要強(qiáng)迫運(yùn)算符的使用者獲得一個(gè)常量值呢?從這個(gè)觀(guān)點(diǎn)看,常量的臨時(shí)對(duì)象看上去就象是自相矛盾的,既是不變的,又是臨時(shí)的。從實(shí)踐的觀(guān)點(diǎn)看,常量對(duì)象強(qiáng)迫復(fù)制。

現(xiàn)在假定我們同意,如果效率是重要的,最好是避免返回值是常量,那么我們?nèi)绾问咕幾g器確信將函數(shù)的結(jié)果轉(zhuǎn)移到目的地,而不是復(fù)制他呢?

當(dāng)復(fù)制一個(gè)類(lèi)型為T的對(duì)象時(shí),拷貝構(gòu)造函數(shù)被調(diào)用。按照下面的設(shè)置,我們剛好可以提供這樣一個(gè)拷貝構(gòu)造函數(shù)實(shí)現(xiàn)這個(gè)目標(biāo)。

class String : public mojo :: enabled < string >
{
//...
public:
  String( String& );
  String( mojo :: temporary < String > );
  String( mojo :: constant < String > );
};

這是一個(gè)很好的設(shè)計(jì),除了一個(gè)小細(xì)節(jié)--它不能工作。

因?yàn)榭截悩?gòu)造函數(shù)和其他的函數(shù)不完全相同,特別是,對(duì)一個(gè)類(lèi)型X來(lái)說(shuō),在需要X(const X&)的地方定義X(X&),下面的代碼將無(wú)法工作:

void FunctionTakingX(const X&);
FunctionTakingX(X());  // 錯(cuò)誤!不能發(fā)現(xiàn)X(const X&)

[譯注]

Wang Tianxinggcc3.2, bcc5.5.1, icl7.0環(huán)境下測(cè)試結(jié)果表明都不會(huì)發(fā)生錯(cuò)誤,并進(jìn)而查閱了標(biāo)準(zhǔn),發(fā)現(xiàn)Andrei是正確的,如果一定說(shuō)要有什么錯(cuò)誤的話(huà),他沒(méi)有指出這是實(shí)現(xiàn)定義的。

8.5.3 References
 
 
5 [...]
 
 
— If the initializer expression is an rvalue, with T2 a class type,
and “cv1 T1” is reference-compatible with “cv2 T2,” the reference
is bound in one of the following ways (the choice is implementation-
defined):
 
 
— The reference is bound to the object represented by the rvalue
(see 3.10) or to a sub-object within that object.
 
 
— A temporary of type “cv1 T2” [sic] is created, and a
constructor is called to copy the entire rvalue object into the
temporary. The reference is bound to the temporary or to a
sub-object within the temporary.93)
 
 
The constructor that would be used to make the copy shall be
callable whether or not the copy is actually done.
 
 
93) Clearly, if the reference initialization being processed is one
for the first argument of a copy constructor call, an implementation
must eventually choose the first alternative (binding without
copying) to avoid infinite recursion.

我引用了這段標(biāo)準(zhǔn)文本,有興趣的讀者可以自行研究它的含義。

這嚴(yán)重的限制了X,所以我們被迫實(shí)現(xiàn)String(const String&)構(gòu)造函數(shù)。現(xiàn)在如果你允許我引用本文的話(huà),在前面我曾經(jīng)說(shuō)過(guò):所以第一個(gè)好主意是不要聲明一個(gè)函數(shù)接受常量引用,因?yàn)樗褚粋€(gè)黑洞一樣吞噬所有的對(duì)象。

魚(yú)與熊掌不可兼得,不是嗎?

很清楚,拷貝構(gòu)造函數(shù)需要特別的處理。這里的想法是創(chuàng)建一個(gè)新的類(lèi)型fnresult,那就是為String對(duì)象提供一個(gè)轉(zhuǎn)移器(mover)”。下面是需要執(zhí)行的步驟:

  1. 前面返回類(lèi)型為T的值的函數(shù)現(xiàn)在將返回fnresult<T>。為了使這個(gè)變化對(duì)對(duì)調(diào)用者透明,fnresult必須可以被隱式的轉(zhuǎn)型為T
  2. 然后為fnresult建立轉(zhuǎn)移語(yǔ)義:無(wú)論何時(shí)一個(gè)fnresult<T>對(duì)象被復(fù)制,里面包含的T被轉(zhuǎn)移。
  3. 類(lèi)似運(yùn)算符的常量性和臨時(shí)性,在mojo::enabled類(lèi)中為fnresult提供一個(gè)轉(zhuǎn)型運(yùn)算符。
  4. 一個(gè)mojo化的類(lèi)(如前例中的String)定義了一個(gè)構(gòu)造函數(shù)String( mojo :: fnresult < String > )完成轉(zhuǎn)移。

這個(gè)fnresult的定義看起來(lái)就像:

namespace mojo
{
  template < class T >
  class fnresult : public T
  {
  public:
    fnresult ( const fnresult& rhs )
      :  T ( temporary < T > ( const_cast < fnresult& > ( rhs ) ) )
    {
    }
    explicit fnresult ( T& rhs ) : T ( temporary < T > ( rhs ) )
    {
    }
  };
}

因?yàn)?span lang="EN-US">fnresult<T>T繼承而來(lái),第一步值得注意,即fnresult<T>轉(zhuǎn)型為T,然后第二個(gè)值得注意的就是復(fù)制fnresult<T>對(duì)象的時(shí)候,隱含著它的T子對(duì)象(subobject)強(qiáng)制轉(zhuǎn)型為temporary<T>

正如前面提到的,我們?cè)黾右粋€(gè)轉(zhuǎn)型允許返回一個(gè)fnresult,最后的版本看起來(lái)是這樣的:

template < class T > struct enabled
{
  operator temporary < T > ( )
  {
    return temporary < T > ( static_cast < T& > ( *this ) );
  }
  operator constant < T > ( ) const
  {
    return constant < T > ( static_cast < const T& > ( *this ) );
  }
  operator fnresult < T > ( )
  {
    return fnresult < T > ( static_cast < T& > ( *this ) );
  }
  protected:
    enabled ( ) { } // intended to be derived from
    ~enabled ( ) { } // intended to be derived from
};

最后是String的定義:

class String : public mojo :: enabled < String >
{
  //...
public:
  // COPY rhs
  String ( const String& rhs ); 
  // MOVE tmp.get() into *this
  String ( mojo :: temporary < String > tmp ); 
  // MOVE res into *this
  String ( mojo :: fnresult < String > res ); 
};

現(xiàn)在考慮下面的函數(shù):

mojo :: fnresult < String > MakeString()
{
  String result;
 //?..
  return result;
}
//...
String dest(MakeString());

MakeStringreturn語(yǔ)句和dest的定義之間的路徑是:

result -> String :: operator fnresult < String > () -> fnresult < String > (const fnresult < String >& ) -> String :: String ( fnresult < String > )

使用RVO的編譯器可以消除調(diào)用鏈中fnresult<String>(const fnresult<String>&)的調(diào)用。然而,更重要的是沒(méi)有函數(shù)執(zhí)行真正的復(fù)制,它們都被定義為結(jié)果的實(shí)際內(nèi)容平滑的轉(zhuǎn)移到dest。也就是說(shuō)沒(méi)有涉及內(nèi)存分配和復(fù)制。

現(xiàn)在,正如所見(jiàn),有兩個(gè),最多三個(gè)轉(zhuǎn)移操作。當(dāng)然,在一定條件和一定類(lèi)型的情況下,一次復(fù)制比三次轉(zhuǎn)移可能更好。還有一個(gè)重要的區(qū)別,復(fù)制也許會(huì)失敗(拋出異常),而轉(zhuǎn)移永遠(yuǎn)不會(huì)失敗。

5 擴(kuò)展

好的,我們使Mojo工作了,而且對(duì)于單獨(dú)的類(lèi)相當(dāng)好。現(xiàn)在怎樣將Mojo擴(kuò)展到組合對(duì)象,它們也許包含大量其他的對(duì)象,而且他們中的一些已經(jīng)是mojo化的。

這個(gè)任務(wù)就是將轉(zhuǎn)移構(gòu)造函數(shù)從類(lèi)傳遞到成員。考慮下面的例子,內(nèi)嵌類(lèi)String在類(lèi)Widget中:

class Widget : public mojo::enabled < Widget >
{
  String name_;
public:
  Widget(mojo::temporary< Widget > src) // source is a temporary
    : name_(mojo::as_temporary(src.get().name_))
  {
    Widget& rhs = src.get();
    //... use rhs to perform a destructive copy ... 
  }
  Widget(mojo::constant< Widget > src) // source is a const
    : name_(src.get().name_) // 譯注:這里原文name_(src.name_)顯然有誤
  {
    Widget& rhs = src;
    //... use rhs to perform a destructive copy ... 
  }
};

在轉(zhuǎn)移構(gòu)造函數(shù)中的name_的初始化使用了一個(gè)重要的Mojo輔助函數(shù):

namespace mojo
{
  template < class T >
  struct traits
  {
    enum { enabled = 
      Loki::SuperSubclassStrict< enabled< T >, T >::value };
    typedef typename 
       Loki::Select< enabled,temporary< T >,T& >::Result temporary;
  };
  template < class T >
  inline typename traits< T >::temporary as_temporary(T& src)
  {
    typedef typename traits< T >::temporary temp;
    return temp(src);
  }
}

as_temporary做的所有事情就是根據(jù)一個(gè)左值創(chuàng)建一個(gè)臨時(shí)對(duì)象。使用這個(gè)方法,類(lèi)成員的轉(zhuǎn)移構(gòu)造函數(shù)被目標(biāo)對(duì)象所調(diào)用。

如果Stringmojo化的,Widget得到他的優(yōu)點(diǎn);如果不是,一個(gè)直接的復(fù)制被執(zhí)行。換句話(huà)說(shuō),如果Stringmojo::enabled<String>的一個(gè)派生類(lèi),那么as_temporary返回一個(gè)mojo::temporary<String>。否則,as_temproary(String& src)是一個(gè)簡(jiǎn)單的函數(shù),帶一個(gè)String&的參數(shù)并返回同樣的String&

6 應(yīng)用:auto_ptr的親戚和mojo化的容器

考慮一個(gè)mojo_ptr類(lèi),它通過(guò)使拷貝構(gòu)造函數(shù)私有而禁止它們:

class mojo_ptr : public mojo::enable< mojo_ptr >
{
  mojo_ptr(const mojo_ptr&); // const sources are NOT accepted
public:
  // source is a temporary
  mojo_ptr(mojo::temporary< mojo_ptr > src) 
  {
    mojo_ptr& rhs = src.get();
    //... use rhs to perform a destructive copy ... 
  }
  // source is a function's result
  mojo_ptr(mojo::fnresult< mojo_ptr > src) 
  {
    mojo_ptr& rhs = src.get();
    //... use rhs to perform a destructive copy ... 
  } 
  //..
};

這個(gè)類(lèi)有一個(gè)有趣的行為。你不能復(fù)制這個(gè)類(lèi)的常量對(duì)象。你也不能復(fù)制這個(gè)類(lèi)的左值。但是你可以復(fù)制這個(gè)類(lèi)的臨時(shí)對(duì)象(使用轉(zhuǎn)移語(yǔ)義),而且你可以顯式的移動(dòng)一個(gè)對(duì)象到另外的對(duì)象:

mojo_ptr ptr1;
mojo_ptr ptr2 = mojo::as_temporary(ptr1);

這本身并沒(méi)有什么大不了的,如果 auto_ptr 里讓 auto_ptr(auto_ptr&)私有,也可以做到這一點(diǎn)。有趣的地方不是mojo_ptr本身,而是如何使用as_temporary。你可以建立高效的容器,儲(chǔ)存經(jīng)典的類(lèi)型、一般的mojo化的類(lèi)型以及和mojo_ptr類(lèi)似的類(lèi)型。所有這樣的一個(gè)容器當(dāng)他需要轉(zhuǎn)移元素時(shí),必須使用as_temporary。對(duì)于經(jīng)典類(lèi)型,as_temporary是一個(gè)什么都不做的等效函數(shù),對(duì)于mojo_ptras_temporary是一個(gè)提供平滑轉(zhuǎn)移機(jī)制的函數(shù)書(shū)。move()以及uninitialized_move()的函數(shù)模板(參見(jiàn)所附代碼,譯注:代碼請(qǐng)到原版鏈接處尋找)也唾手可得。

使用標(biāo)準(zhǔn)術(shù)語(yǔ),mojo_ptr既不是可以復(fù)制的,也不是可以賦值的。然而,mojo_ptr可以看作是一種新類(lèi)型,叫做可轉(zhuǎn)移的。這是一個(gè)重要的新的分類(lèi),也許可以用于鎖(lock)、文件(file)和其他的不可復(fù)制的句柄(handle)

如果你曾經(jīng)希望一個(gè)擁有元素的類(lèi)似于 vector< auto_ptr<Widget> > 的容器,而且有安全、清楚的語(yǔ)義,現(xiàn)在你得到了,而且還有其他功能。另外,當(dāng)包含一個(gè)拷貝昂貴的類(lèi)型時(shí),如vector< vector<string> >mojo化的vector“更能適應(yīng)元素個(gè)數(shù)增減的需要

7 結(jié)論

mojo是一種技術(shù),也是一個(gè)緊湊的小框架,用于消除不必要的臨時(shí)對(duì)象的復(fù)制。mojo的工作方式是檢測(cè)臨時(shí)對(duì)象并且通過(guò)函數(shù)重載操縱他們而不是簡(jiǎn)單的作為左值。這樣做的結(jié)果是,獲得臨時(shí)對(duì)象的函數(shù)執(zhí)行一個(gè)破壞性的復(fù)制,只要確信其他代碼不再使用這個(gè)臨時(shí)對(duì)象即可。

如果客戶(hù)代碼按照一套簡(jiǎn)單的規(guī)則傳遞函數(shù)參數(shù)和返回值,可以應(yīng)用mojo

mojo定義了一個(gè)單獨(dú)的機(jī)制來(lái)消除函數(shù)返回時(shí)的復(fù)制。

額外的機(jī)制和類(lèi)型轉(zhuǎn)換使mojo對(duì)于客戶(hù)代碼不是100%的透明,然而對(duì)于基于庫(kù)的解決方案來(lái)說(shuō)集成度是相當(dāng)好的。說(shuō)得好聽(tīng)一點(diǎn),mojo將作為一個(gè)健壯的替代品,直到一個(gè)更健壯的、基于語(yǔ)言特性的被標(biāo)準(zhǔn)化并實(shí)現(xiàn)。

8 致謝

原文的致謝略,譯文得到了Wang Tianxing的熱情幫助,除了幫助我審核了若干技術(shù)細(xì)節(jié)之外,還指出了不少打字錯(cuò)誤,以及若干英語(yǔ)中的諺語(yǔ)。

9 參考文獻(xiàn)

[1] Dov Bulka and David Mayhew. Efficient C++: Performance Programming Techniques, (Addison-Wesley, 1999).

[2] Howard E. Hinnant, Peter Dimov, and Dave Abrahams. "A Proposal to Add Move Semantics Support to the C++ Language," ISO/IEC JTC1/SC22/WG21 — C++, document number N1377=02-0035, September 2002, <http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1377.htm>.

[3] "Programming Languages — C++," International Standard ISO/IEC 14882, Section 12.2.

[4] Herb Sutter. More Exceptional C++ (Addison-Wesley, 2002).

[5] Andrei Alexandrescu. Modern C++ Design (Addison-Wesley, 2001).

[6] John Lakos. Large-Scale C++ Software Design (Addison-Wesley, 1996), Section 9.1.9.

[7] Herb Sutter. Exceptional C++ (Addison-Wesley, 2000).

作者簡(jiǎn)介

Andrei Alexandrescu是一位華盛頓大學(xué)西雅圖分校的博士生,廣受贊譽(yù)的《Modern C++ Design》(中譯本現(xiàn)代C++設(shè)計(jì)正在譯制中)一書(shū)的作者。可以通過(guò)電子郵件andrei@metalanguage.com聯(lián)系。Andrei還是一個(gè)C++課程的有號(hào)召力的講師。

譯者的話(huà)

作為第一次編譯技術(shù)文章,而且選擇的是C++中自己相對(duì)比較陌生的主題,并且本文講述的內(nèi)容是具有前瞻性的,而不是見(jiàn)諸于現(xiàn)有資料和文獻(xiàn)的重新整理。因此在翻譯過(guò)程中,有些細(xì)節(jié)譯者本人也沒(méi)有完全理解,因此難免出現(xiàn)不少差錯(cuò),歡迎大家來(lái)到newsfanC++新聞組討論

 

posted on 2009-12-02 17:14 肥仔 閱讀(1627) 評(píng)論(2)  編輯 收藏 引用 所屬分類(lèi): C++ 基礎(chǔ)

評(píng)論

# re: 泛型編程-轉(zhuǎn)移構(gòu)造函數(shù)(Generic Programming: Move Constructor)  回復(fù)  更多評(píng)論   

學(xué)習(xí)了
2012-02-16 15:23 | 溪流

# re: 泛型編程-轉(zhuǎn)移構(gòu)造函數(shù)(Generic Programming: Move Constructor)  回復(fù)  更多評(píng)論   

借地問(wèn)大家一個(gè)問(wèn)題:
const r-value 存在嗎?怎么構(gòu)造?
2012-02-16 16:18 | 溪流
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            免费短视频成人日韩| 久久琪琪电影院| 欧美视频在线免费看| 免费观看在线综合| 欧美电影在线| 欧美午夜宅男影院| 国产精品久久久爽爽爽麻豆色哟哟| 欧美日韩美女在线| 国产一区二区精品在线观看| 国色天香一区二区| 亚洲区在线播放| 亚洲一区二区三区国产| 欧美资源在线| 亚洲国产99| 亚洲视频1区| 欧美高清视频一二三区| 国产亚洲欧洲997久久综合| 国精品一区二区三区| 一区二区免费在线播放| 美女视频黄a大片欧美| 亚洲一区二区三区高清不卡| 嫩草影视亚洲| 亚洲国产成人精品久久| 久久精品动漫| 亚洲一区二区精品视频| 欧美精品在线观看播放| 在线看无码的免费网站| 欧美在线播放一区二区| 亚洲专区在线| 国产专区欧美专区| 久久国产日本精品| 欧美一区二区三区在线播放| 欧美视频一区二区三区…| 亚洲欧美久久| 在线视频日韩| 亚洲国产清纯| 校园春色国产精品| 欧美一区二区三区视频免费播放 | 美日韩精品视频免费看| 欧美怡红院视频一区二区三区| 国产精品va在线| 欧美与欧洲交xxxx免费观看| 亚洲欧美日韩精品综合在线观看| 国产精品每日更新| 久久永久免费| 欧美日韩精品在线视频| 午夜亚洲影视| 久久手机精品视频| 99精品久久| 欧美怡红院视频一区二区三区| 狠狠爱综合网| 妖精视频成人观看www| 国产一区二区精品| 欧美成人在线免费观看| 国产精品白丝jk黑袜喷水| 久久久久欧美精品| 欧美日韩国语| 欧美成人福利视频| 国产精品青草综合久久久久99 | 在线观看国产一区二区| 亚洲美女免费视频| 亚洲电影第1页| 欧美永久精品| 久久国产精品久久久久久电车| 欧美精品色综合| 欧美激情一区二区三区在线| 亚洲一区二区在线免费观看视频 | 另类av导航| 欧美国产丝袜视频| 激情综合激情| 蜜桃伊人久久| 欧美激情综合| 夜夜嗨av一区二区三区中文字幕 | 久久av一区二区| 久久福利毛片| 亚洲福利视频网站| 久久中文精品| 亚洲欧洲日产国产网站| 亚洲电影观看| 国产精品高潮呻吟久久| 亚洲欧美日韩精品久久亚洲区 | 亚洲第一在线综合在线| 免费观看成人| 亚洲午夜成aⅴ人片| 久久精品久久综合| 亚洲精选一区二区| 国产精品福利网| 久久九九精品99国产精品| 久久亚洲一区二区三区四区| 亚洲综合欧美| 久久精品国产清高在天天线| 国产精品扒开腿爽爽爽视频| 亚洲激情不卡| 亚洲精品久久久久| 亚洲图片欧洲图片av| 亚洲一区二区三区视频| 在线亚洲免费视频| 亚洲视频一二| 久久大香伊蕉在人线观看热2| 午夜国产精品视频免费体验区| 黄色成人av网站| 国产精品久久久久久久久免费| 欧美国产乱视频| 欧美在线观看网址综合| 亚洲激情欧美| 欧美成人视屏| 久久夜色精品| 欧美亚洲视频在线看网址| 亚洲精品久久久久久久久| 亚洲精品视频在线观看网站| 国产精品免费福利| 国产精品久久久一区麻豆最新章节 | 亚洲女性裸体视频| 99国产精品| 日韩亚洲欧美成人一区| 亚洲人成艺术| 一本大道久久a久久精品综合 | 国产精品一区二区久久精品 | 欧美伊人久久| 欧美精品乱码久久久久久按摩| 欧美电影在线观看| 欧美三区在线视频| 在线观看欧美视频| 亚洲天堂av在线免费观看| 在线视频日本亚洲性| 久久国产精品99国产精| 欧美护士18xxxxhd| 亚洲欧美日韩精品久久亚洲区 | 很黄很黄激情成人| 在线看不卡av| 久久aⅴ国产紧身牛仔裤| 亚洲经典在线| 久久精品在线视频| 国产精品卡一卡二| 亚洲精品国产精品国自产观看| 久久精品五月| 亚洲女人天堂成人av在线| 久久久人成影片一区二区三区 | 久久国产毛片| 国产毛片一区| 欧美中文字幕在线视频| 亚洲天堂视频在线观看| 欧美精品导航| 99精品视频网| 免费不卡欧美自拍视频| 久久婷婷综合激情| 亚洲欧美另类中文字幕| 国产精品夫妻自拍| 午夜精品视频| 亚洲一区二区三区午夜| 国产精品每日更新| 久久综合激情| 免费中文字幕日韩欧美| 在线播放豆国产99亚洲| 91久久国产综合久久蜜月精品| 销魂美女一区二区三区视频在线| 久久激情中文| 久久久免费观看视频| 国产免费亚洲高清| 亚洲图片在线观看| 亚洲欧美精品在线| 国产色综合天天综合网| 亚洲欧美清纯在线制服| 亚洲永久免费视频| 国产精品成人在线| 免费亚洲电影| 国产一区白浆| 亚洲婷婷综合久久一本伊一区| 亚洲国产日韩欧美在线99| 欧美在线999| 久久精品国产77777蜜臀| 日韩视频中文| 性久久久久久久久| 亚洲综合三区| 欧美国产欧美亚州国产日韩mv天天看完整| 中国成人在线视频| 欧美另类videos死尸| 嫩草影视亚洲| 狠狠久久亚洲欧美专区| 亚洲一区欧美激情| 亚洲精品乱码久久久久久蜜桃麻豆| 欧美在线一区二区三区| 欧美一区二区视频网站| 欧美精品九九| 亚洲激情黄色| 99精品国产高清一区二区| 欧美精品手机在线| 亚洲国产天堂久久国产91| 亚洲黑丝在线| 欧美国产成人在线| 99国产精品久久久久久久久久| 99国产成+人+综合+亚洲欧美| 午夜久久tv| 欧美.www| 夜久久久久久| 国产一二三精品| 另类尿喷潮videofree| 亚洲久久一区二区| 国产精品h在线观看| 久久爱www|