C++11右值引用和移動語義的實(shí)例解析_第1頁
C++11右值引用和移動語義的實(shí)例解析_第2頁
C++11右值引用和移動語義的實(shí)例解析_第3頁
C++11右值引用和移動語義的實(shí)例解析_第4頁
C++11右值引用和移動語義的實(shí)例解析_第5頁
已閱讀5頁,還剩21頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

第C++11右值引用和移動語義的實(shí)例解析目錄基本概念左值vs右值左值引用vs右值引用右值引用使用場景和意義左值引用的使用場景左值引用的短板右值引用和移動語義右值引用引用左值右值引用的其他使用場景完美轉(zhuǎn)發(fā)萬能引用完美轉(zhuǎn)發(fā)保持值的屬性完美轉(zhuǎn)發(fā)的使用場景總結(jié)

基本概念

左值vs右值

什么是左值?

左值是一個表示數(shù)據(jù)的表達(dá)式,如變量名或解引用的指針。

左值可以被取地址,也可以被修改(const修飾的左值除外)。左值可以出現(xiàn)在賦值符號的左邊,也可以出現(xiàn)在賦值符號的右邊。

intmain()

//以下的p、b、c、*p都是左值

int*p=newint(0);

intb=1;

constintc=2;

return0;

什么是右值?

右值也是一個表示數(shù)據(jù)的表達(dá)式,如字母常量、表達(dá)式的返回值、函數(shù)的返回值(不能是左值引用返回)等等。

右值不能被取地址,也不能被修改。右值可以出現(xiàn)在賦值符號的右邊,但是不能出現(xiàn)在賦值符號的左邊。

intmain()

doublex=1.1,y=2.2;

//以下幾個都是常見的右值

x+y;

fmin(x,y);

//錯誤示例(右值不能出現(xiàn)在賦值符號的左邊)

//10=1;

//x+y=1;

//fmin(x,y)=1;

return0;

右值本質(zhì)就是一個臨時變量或常量值,比如代碼中的10就是常量值,表達(dá)式x+y和函數(shù)fmin的返回值就是臨時變量,這些都叫做右值。這些臨時變量和常量值并沒有被實(shí)際存儲起來,這也就是為什么右值不能被取地址的原因,因?yàn)橹挥斜淮鎯ζ饋砗蟛庞械刂?。但需要注意的是,這里說函數(shù)的返回值是右值,指的是傳值返回的函數(shù),因?yàn)閭髦捣祷氐暮瘮?shù)在返回對象時返回的是對象的拷貝,這個拷貝出來的對象就是一個臨時變量。

而對于左值引用返回的函數(shù)來說,這些函數(shù)返回的是左值。比如string類實(shí)現(xiàn)的[]運(yùn)算符重載函數(shù):

namespacecl

//模擬實(shí)現(xiàn)string類

classstring

public:

//[]運(yùn)算符重載(可讀可寫)

charoperator[](size_ti)

assert(i_size);//檢測下標(biāo)的合法性

return_str[i];//返回對應(yīng)字符

//...

private:

char*_str;//存儲字符串

size_t_size;//記錄字符串當(dāng)前的有效長度

//...

intmain()

cl::strings("hello");

s[3]='x';//引用返回,支持外部修改

return0;

這里的[]運(yùn)算符重載函數(shù)返回的是一個字符的引用,因?yàn)樗枰С滞獠繉υ撐恢玫淖址M(jìn)行修改,所以必須采用左值引用返回。之所以說這里返回的是一個左值,是因?yàn)檫@個返回的字符是被存儲起來了的,是存儲在string對象的_str對象當(dāng)中的,因此這個字符是可以被取到地址的。

左值引用vs右值引用

傳統(tǒng)的C++語法中就有引用的語法,而C++11中新增了右值引用的語法特性,為了進(jìn)行區(qū)分,于是將C++11之前的引用就叫做左值引用。但是無論左值引用還是右值引用,本質(zhì)都是給對象取別名。

左值引用

左值引用就是對左值的引用,給左值取別名,通過來聲明。比如:

intmain()

//以下的p、b、c、*p都是左值

int*p=newint(0);

intb=1;

constintc=2;

//以下幾個是對上面左值的左值引用

int*rp=p;

intrb=b;

constintrc=c;

intpvalue=*p;

return0;

右值引用

右值引用就是對右值的引用,給右值取別名,通過來聲明。比如:

intmain()

doublex=1.1,y=2.2;

//以下幾個都是常見的右值

x+y;

fmin(x,y);

//以下幾個都是對右值的右值引用

intrr1=10;

doublerr2=x+y;

doublerr3=fmin(x,y);

return0;

需要注意的是,右值是不能取地址的,但是給右值取別名后,會導(dǎo)致右值被存儲到特定位置,這時這個右值可以被取到地址,并且可以被修改,如果不想讓被引用的右值被修改,可以用const修飾右值引用。比如:

intmain()

doublex=1.1,y=2.2;

intrr1=10;

constdoublerr2=x+y;

rr1=20;

rr2=5.5;//報錯

return0;

左值引用可以引用右值嗎?

左值引用不能引用右值,因?yàn)檫@涉及權(quán)限放大的問題,右值是不能被修改的,而左值引用是可以修改。但是const左值引用可以引用右值,因?yàn)閏onst左值引用能夠保證被引用的數(shù)據(jù)不會被修改。

因此const左值引用既可以引用左值,也可以引用右值。比如:

templateclassT

voidfunc(constTval)

coutvalendl;

intmain()

strings("hello");

func(s);//s為左值

func("world");//"world"為右值

return0;

右值引用可以引用左值嗎?

右值引用只能引用右值,不能引用左值。但是右值引用可以引用move以后的左值。

move函數(shù)是C++11標(biāo)準(zhǔn)提供的一個函數(shù),被move后的左值能夠賦值給右值引用。比如:

intmain()

inta=10;

//intr1=a;//右值引用不能引用左值

intr2=move(a);//右值引用可以引用move以后的左值

return0;

右值引用使用場景和意義

雖然const左值引用既能接收左值,又能接收右值,但左值引用終究存在短板,而C++11提出的右值引用就是用來解決左值引用的短板的。

為了更好的說明問題,這里需要借助一個深拷貝的類,下面模擬實(shí)現(xiàn)了一個簡化版的string類。類當(dāng)中實(shí)現(xiàn)了一些基本的成員函數(shù),并在string的拷貝構(gòu)造函數(shù)和賦值運(yùn)算符重載函數(shù)當(dāng)中打印了一條提示語句,這樣當(dāng)調(diào)用這兩個函數(shù)時我們就能夠知道。

代碼如下:

namespacecl

classstring

public:

typedefchar*iterator;

iteratorbegin()

return_str;//返回字符串中第一個字符的地址

iteratorend()

return_str+_size;//返回字符串中最后一個字符的后一個字符的地址

//構(gòu)造函數(shù)

string(constchar*str="")

_size=strlen(str);//初始時,字符串大小設(shè)置為字符串長度

_capacity=_size;//初始時,字符串容量設(shè)置為字符串長度

_str=newchar[_capacity+1];//為存儲字符串開辟空間(多開一個用于存放'\0')

strcpy(_str,str);//將C字符串拷貝到已開好的空間

//交換兩個對象的數(shù)據(jù)

voidswap(strings)

//調(diào)用庫里的swap

::swap(_str,s._str);//交換兩個對象的C字符串

::swap(_size,s._size);//交換兩個對象的大小

::swap(_capacity,s._capacity);//交換兩個對象的容量

//拷貝構(gòu)造函數(shù)(現(xiàn)代寫法)

string(conststrings)

:_str(nullptr)

,_size(0)

,_capacity(0)

cout"string(conststrings)--深拷貝"endl;

stringtmp(s._str);//調(diào)用構(gòu)造函數(shù),構(gòu)造出一個C字符串為s._str的對象

swap(tmp);//交換這兩個對象

//賦值運(yùn)算符重載(現(xiàn)代寫法)

stringoperator=(conststrings)

cout"stringoperator=(conststrings)--深拷貝"endl;

stringtmp(s);//用s拷貝構(gòu)造出對象tmp

swap(tmp);//交換這兩個對象

return*this;//返回左值(支持連續(xù)賦值)

//析構(gòu)函數(shù)

~string()

delete[]_str;//釋放_str指向的空間

_str=nullptr;//及時置空,防止非法訪問

_size=0;//大小置0

_capacity=0;//容量置0

//[]運(yùn)算符重載

charoperator[](size_ti)

assert(i_size);//檢測下標(biāo)的合法性

return_str[i];//返回對應(yīng)字符

//改變?nèi)萘浚笮〔蛔?/p>

voidreserve(size_tn)

if(n_capacity)//當(dāng)n大于對象當(dāng)前容量時才需執(zhí)行操作

char*tmp=newchar[n+1];//多開一個空間用于存放'\0'

strncpy(tmp,_str,_size+1);//將對象原本的C字符串拷貝過來(包括'\0')

delete[]_str;//釋放對象原本的空間

_str=tmp;//將新開辟的空間交給_str

_capacity=n;//容量跟著改變

//尾插字符

voidpush_back(charch)

if(_size==_capacity)//判斷是否需要增容

reserve(_capacity==04:_capacity*2);//將容量擴(kuò)大為原來的兩倍

_str[_size]=ch;//將字符尾插到字符串

_str[_size+1]='\0';//字符串后面放上'\0'

_size++;//字符串的大小加一

//+=運(yùn)算符重載

stringoperator+=(charch)

push_back(ch);//尾插字符串

return*this;//返回左值(支持連續(xù)+=)

//返回C類型的字符串

constchar*c_str()const

return_str;

private:

char*_str;

size_t_size;

size_t_capacity;

左值引用的使用場景

在說明左值引用的短板之前,我們先來看看左值引用的使用場景:

左值引用做參數(shù),防止傳參時進(jìn)行拷貝操作。左值引用做返回值,防止返回時對返回對象進(jìn)行拷貝操作。

voidfunc1(cl::strings)

voidfunc2(constcl::strings)

intmain()

cl::strings("helloworld");

func1(s);//值傳參

func2(s);//左值引用傳參

s+='X';//左值引用返回

return0;

因?yàn)槲覀兡M實(shí)現(xiàn)是string類的拷貝構(gòu)造函數(shù)當(dāng)中打印了提示語句,因此運(yùn)行代碼后通過程序運(yùn)行結(jié)果就知道,值傳參時調(diào)用了string的拷貝構(gòu)造函數(shù)。

此外,因?yàn)閟tring的+=運(yùn)算符重載函數(shù)是左值引用返回的,因此在返回+=后的對象時不會調(diào)用拷貝構(gòu)造函數(shù),但如果將+=運(yùn)算符重載函數(shù)改為傳值返回,那么重新運(yùn)行代碼后你就會發(fā)現(xiàn)多了一次拷貝構(gòu)造函數(shù)的調(diào)用。

我們都知道string的拷貝是深拷貝,深拷貝的代價是比較高的,我們應(yīng)該盡量避免不必要的深拷貝操作,因此這里左值引用起到的作用還是很明顯的。

左值引用的短板

左值引用雖然能避免不必要的拷貝操作,但左值引用并不能完全避免。

左值引用做參數(shù),能夠完全避免傳參時不必要的拷貝操作。左值引用做返回值,并不能完全避免函數(shù)返回對象時不必要的拷貝操作。

如果函數(shù)返回的對象是一個局部變量,該變量出了函數(shù)作用域就被銷毀了,這種情況下不能用左值引用作為返回值,只能以傳值的方式返回,這就是左值引用的短板。

比如下面我們模擬實(shí)現(xiàn)一個int版本的to_string函數(shù),這個to_string函數(shù)就不能使用左值引用返回,因?yàn)閠o_string函數(shù)返回的是一個局部變量。

代碼如下:

namespacecl

cl::stringto_string(intvalue)

boolflag=true;

if(value0)

flag=false;

value=0-value;

cl::stringstr;

while(value0)

intx=value%10;

value/=10;

str+=(x+'0');

if(flag==false)

str+='-';

std::reverse(str.begin(),str.end());

returnstr;

此時調(diào)用to_string函數(shù)返回時,就一定會調(diào)用string的拷貝構(gòu)造函數(shù)。比如:

intmain()

cl::strings=cl::to_string(1234);

return0;

C++11提出右值引用就是為了解決左值引用的這個短板的,但解決方式并不是簡單的將右值引用作為函數(shù)的返回值。

右值引用和移動語義

右值引用和移動語句解決上述問題的方式就是,給當(dāng)前模擬實(shí)現(xiàn)的string類增加移動構(gòu)造和移動賦值方法。

移動構(gòu)造

移動構(gòu)造是一個構(gòu)造函數(shù),該構(gòu)造函數(shù)的參數(shù)是右值引用類型的,移動構(gòu)造本質(zhì)就是將傳入右值的資源竊取過來,占為己有,這樣就避免了進(jìn)行深拷貝,所以它叫做移動構(gòu)造,就是竊取別人的資源來構(gòu)造自己的意思。

在當(dāng)前的string類中增加一個移動構(gòu)造函數(shù),該函數(shù)要做的就是調(diào)用swap函數(shù)將傳入右值的資源竊取過來,為了能夠更好的得知移動構(gòu)造函數(shù)是否被調(diào)用,可以在該函數(shù)當(dāng)中打印一條提示語句。

代碼如下:

namespacecl

classstring

public:

//移動構(gòu)造

string(strings)

:_str(nullptr)

,_size(0)

,_capacity(0)

cout"string(strings)--移動構(gòu)造"endl;

swap(s);

private:

char*_str;

size_t_size;

size_t_capacity;

移動構(gòu)造和拷貝構(gòu)造的區(qū)別:

在沒有增加移動構(gòu)造之前,由于拷貝構(gòu)造采用的是const左值引用接收參數(shù),因此無論拷貝構(gòu)造對象時傳入的是左值還是右值,都會調(diào)用拷貝構(gòu)造函數(shù)。增加移動構(gòu)造之后,由于移動構(gòu)造采用的是右值引用接收參數(shù),因此如果拷貝構(gòu)造對象時傳入的是右值,那么就會調(diào)用移動構(gòu)造函數(shù)(最匹配原則)。string的拷貝構(gòu)造函數(shù)做的是深拷貝,而移動構(gòu)造函數(shù)中只需要調(diào)用swap函數(shù)進(jìn)行資源的轉(zhuǎn)移,因此調(diào)用移動構(gòu)造的代價比調(diào)用拷貝構(gòu)造的代價小。

給string類增加移動構(gòu)造后,對于返回局部string對象的這類函數(shù),在返回string對象時就會調(diào)用移動構(gòu)造進(jìn)行資源的移動,而不會再調(diào)用拷貝構(gòu)造函數(shù)進(jìn)行深拷貝了。比如:

intmain()

cl::strings=cl::to_string(1234);

return0;

說明一下:

雖然to_string當(dāng)中返回的局部string對象是一個左值,但由于該string對象在當(dāng)前函數(shù)調(diào)用結(jié)束后就會立即被銷毀,我可以把這種即將被消耗的值叫做將亡值,比如匿名對象也可以叫做將亡值。既然將亡值馬上就要被銷毀了,那還不如把它的資源轉(zhuǎn)移給別人用,因此編譯器在識別這種將亡值時會將其識別為右值,這樣就可以匹配到參數(shù)類型為右值引用的移動構(gòu)造函數(shù)。

編譯器做的優(yōu)化

實(shí)際當(dāng)一個函數(shù)在返回局部對象時,會先用這個局部對象拷貝構(gòu)造出一個臨時對象,然后再用這個臨時對象來拷貝構(gòu)造我們接收返回值的對象。如下:

因此在C++11標(biāo)準(zhǔn)出來之前,對于深拷貝的類來說這里就會進(jìn)行兩次深拷貝,所以大部分編譯器為了提高效率都對這種情況進(jìn)行了優(yōu)化,這種連續(xù)調(diào)用構(gòu)造函數(shù)的場景通常會被優(yōu)化成一次。比如:

因此按道理來說,在C++11標(biāo)準(zhǔn)出來之前這里應(yīng)該調(diào)用兩次string的拷貝構(gòu)造函數(shù),但最終被編譯器優(yōu)化成了一次,減少了一次無意義的深拷貝。(并不是所有的編譯器都做了這個優(yōu)化)

在C++11出來之后,編譯器的這個優(yōu)化仍然起到了作用。

如果編譯器不優(yōu)化這里應(yīng)該調(diào)用兩次移動構(gòu)造,第一次調(diào)用移動構(gòu)造用返回的局部string對象構(gòu)造出一個臨時對象,第二次調(diào)用移動構(gòu)造用這個臨時對象構(gòu)造接收返回值的對象。而經(jīng)過編譯器優(yōu)化后,最終這兩次移動構(gòu)造就被優(yōu)化成了一次,也就是直接將返回的局部string對象的資源移動給了接收返回值的對象。此外,C++11之后就算編譯器沒有進(jìn)行這個優(yōu)化問題也不大,因?yàn)椴粌?yōu)化也就是調(diào)用兩次移動構(gòu)造進(jìn)行兩次資源的轉(zhuǎn)移而已。

但如果我們不是用函數(shù)的返回值來構(gòu)造一個對象,而是用一個之前已經(jīng)定義出來的對象來接收函數(shù)的返回值,這時編譯器就無法進(jìn)行優(yōu)化了。比如:

這時當(dāng)函數(shù)返回局部對象時,會先用這個局部對象拷貝構(gòu)造出一個臨時對象,然后再調(diào)用賦值運(yùn)算符重載函數(shù)將這個臨時對象賦值給接收函數(shù)返回值的對象。

編譯器并沒有對這種情況進(jìn)行優(yōu)化,因此在C++11標(biāo)準(zhǔn)出來之前,對于深拷貝的類來說這里就會存在兩次深拷貝,因?yàn)樯羁截惖念惖馁x值運(yùn)算符重載函數(shù)也需要以深拷貝的方式實(shí)現(xiàn)。但在深拷貝的類中引入C++11的移動構(gòu)造后,這里仍然需要再調(diào)用一次賦值運(yùn)算符重載函數(shù)進(jìn)行深拷貝,因此深拷貝的類不僅需要實(shí)現(xiàn)移動構(gòu)造,還需要實(shí)現(xiàn)移動賦值。

這里需要說明的是,對于返回局部對象的函數(shù),就算只是調(diào)用函數(shù)而不接收該函數(shù)的返回值,也會存在一次拷貝構(gòu)造或移動構(gòu)造,因?yàn)楹瘮?shù)的返回值不管你接不接收都必須要有,而當(dāng)函數(shù)結(jié)束后該函數(shù)內(nèi)的局部對象都會被銷毀,所以就算不接收函數(shù)的返回值也會調(diào)用一次拷貝構(gòu)造或移動構(gòu)造生成臨時對象。

移動賦值

移動賦值是一個賦值運(yùn)算符重載函數(shù),該函數(shù)的參數(shù)是右值引用類型的,移動賦值也是將傳入右值的資源竊取過來,占為己有,這樣就避免了深拷貝,所以它叫移動賦值,就是竊取別人的資源來賦值給自己的意思。

在當(dāng)前的string類中增加一個移動賦值函數(shù),該函數(shù)要做的就是調(diào)用swap函數(shù)將傳入右值的資源竊取過來,為了能夠更好的得知移動賦值函數(shù)是否被調(diào)用,可以在該函數(shù)中打印一條提示語句。

代碼如下:

namespacecl

classstring

public:

//移動賦值

stringoperator=(strings)

cout"stringoperator=(strings)--移動賦值"endl;

swap(s);

return*this;

private:

char*_str;

size_t_size;

size_t_capacity;

移動賦值和原有operator=函數(shù)的區(qū)別:

在沒有增加移動賦值之前,由于原有operator=函數(shù)采用的是const左值引用接收參數(shù),因此無論賦值時傳入的是左值還是右值,都會調(diào)用原有的operator=函數(shù)。增加移動賦值之后,由于移動賦值采用的是右值引用接收參數(shù),因此如果賦值時傳入的是右值,那么就會調(diào)用移動賦值函數(shù)(最匹配原則)。string原有的operator=函數(shù)做的是深拷貝,而移動賦值函數(shù)中只需要調(diào)用swap函數(shù)進(jìn)行資源的轉(zhuǎn)移,因此調(diào)用移動賦值的代價比調(diào)用原有operator=的代價小。

現(xiàn)在給string增加移動構(gòu)造和移動賦值以后,就算是用一個已經(jīng)定義過的string對象去接收to_string函數(shù)的返回值,此時也不會存在深拷貝。比如:

intmain()

cl::strings;

//...

s=cl::to_string(1234);

return0;

此時當(dāng)to_string函數(shù)返回局部的string對象時,會先調(diào)用移動構(gòu)造生成一個臨時對象,然后再調(diào)用移動賦值將臨時對象的資源轉(zhuǎn)移給我們接收返回值的對象,這個過程雖然調(diào)用了兩個函數(shù),但這兩個函數(shù)要做的只是資源的移動,而不需要進(jìn)行深拷貝,大大提高了效率。

說明一下:在實(shí)現(xiàn)移動賦值函數(shù)之前,該代碼的運(yùn)行結(jié)果理論上應(yīng)該是調(diào)用一次拷貝構(gòu)造,再調(diào)用一次原有的operator=函數(shù),但由于原有operator=函數(shù)實(shí)現(xiàn)時復(fù)用了拷貝構(gòu)造函數(shù),因此代碼運(yùn)行后的輸出結(jié)果會多打印一次拷貝構(gòu)造函數(shù)的調(diào)用,這是原有operator=函數(shù)內(nèi)部調(diào)用的。

STL中的容器

C++11標(biāo)準(zhǔn)出來之后,STL中的容器都增加了移動構(gòu)造和移動賦值。

以我們剛剛說的string類為例,這是string類增加的移動構(gòu)造:

這是string類增加的移動賦值:

右值引用引用左值

右值引用雖然不能引用左值,但也不是完全不可以,當(dāng)需要用右值引用引用一個左值時,可以通過move函數(shù)將左值轉(zhuǎn)化為右值。

move函數(shù)的名字具有迷惑性,move函數(shù)實(shí)際并不能搬移任何東西,該函數(shù)唯一的功能就是將一個左值強(qiáng)制轉(zhuǎn)化為右值引用,然后實(shí)現(xiàn)移動語義。

move函數(shù)的定義如下:

templateclass_Ty

inlinetypenameremove_reference_Ty::typemove(_Ty_Arg)_NOEXCEPT

//forward_Argasmovable

return((typenameremove_reference_Ty::type)_Arg);

說明一下:

move函數(shù)中_Arg參數(shù)的類型不是右值引用,而是萬能引用。萬能引用跟右值引用的形式一樣,但是右值引用需要是確定的類型。一個左值被move以后,它的資源可能就被轉(zhuǎn)移給別人了,因此要慎用一個被move后的左值。

右值引用的其他使用場景

右值引用版本的插入函數(shù)

C++11標(biāo)準(zhǔn)出來之后,STL中的容器除了增加移動構(gòu)造和移動賦值之外,STL容器插入接口函數(shù)也增加了右值引用版本。

以list容器的push_back接口為例:

右值引用版本插入函數(shù)的意義

如果list容器當(dāng)中存儲的是string對象,那么在調(diào)用push_back向list容器中插入元素時,可能會有如下幾種插入方式:

intmain()

listcl::string

cl::strings("1111");

lt.push_back(s);//調(diào)用string的拷貝構(gòu)造

lt.push_back("2222");//調(diào)用string的移動構(gòu)造

lt.push_back(cl::string("3333"));//調(diào)用string的移動構(gòu)造

lt.push_back(std::move(s));//調(diào)用string的移動構(gòu)造

return0;

list容器的push_back函數(shù)需要先構(gòu)造一個結(jié)點(diǎn),然后將該結(jié)點(diǎn)插入到底層的雙鏈表當(dāng)中。

在C++11之前l(fā)ist容器的push_back接口只有一個左值引用版本,因此在push_back函數(shù)中構(gòu)造結(jié)點(diǎn)時,這個左值只能匹配到string的拷貝構(gòu)造函數(shù)進(jìn)行深拷貝。而在C++11出來之后,string類提供了移動構(gòu)造函數(shù),并且list容器的push_back接口提供了右值引用版本,此時如果傳入push_back函數(shù)的string對象是一個右值,那么在push_back函數(shù)中構(gòu)造結(jié)點(diǎn)時,這個右值就可以匹配到string的移動構(gòu)造函數(shù)進(jìn)行資源的轉(zhuǎn)移,這樣就避免了深拷貝,提高了效率。上述代碼中的插入第一個元素時就會匹配到push_back的左值引用版本,在push_back函數(shù)內(nèi)部就會調(diào)用string的拷貝構(gòu)造函數(shù)進(jìn)行深拷貝,而插入后面三個元素時由于傳入的是右值,因此會匹配到push_back的右值引用版本,此時在push_back函數(shù)內(nèi)部就會調(diào)用string的移動構(gòu)造函數(shù)進(jìn)行資源的轉(zhuǎn)移。

完美轉(zhuǎn)發(fā)

萬能引用

模板中的不代表右值引用,而是萬能引用,其既能接收左值又能接收右值。比如:

templateclassT

voidPerfectForward(Tt)

//...

右值引用和萬能引用的區(qū)別就是,右值引用需要是確定的類型,而萬能引用是根據(jù)傳入實(shí)參的類型進(jìn)行推導(dǎo),如果傳入的實(shí)參是一個左值,那么這里的形參t就是左值引用,如果傳入的實(shí)參是一個右值,那么這里的形參t就是右值引用。

下面重載了四個Func函數(shù),這四個Func函數(shù)的參數(shù)類型分別是左值引用、const左值引用、右值引用和const右值引用。在主函數(shù)中調(diào)用PerfectForward函數(shù)時分別傳入左值、右值、const左值和const右值,在PerfectForward函數(shù)中再調(diào)用Func函數(shù)。如下:

voidFunc(intx)

cout"左值引用"endl;

voidFunc(constintx)

cout"const左值引用"endl;

voidFunc(intx)

cout"右值引用"endl;

voidFunc(constintx)

cout"const右值引用"endl;

templateclassT

voidPerfectForward(Tt)

Func(t);

intmain()

inta=10;

PerfectForward(a);//左值

PerfectForward(move(a));//右值

constintb=20;

PerfectForward(b);//const左值

PerfectForward(move(b));//const右值

return0;

由于PerfectForward函數(shù)的參數(shù)類型是萬能引用,因此既可以接收左值也可以接收右值,而我們在PerfectForward函數(shù)中調(diào)用Func函數(shù),就是希望調(diào)用PerfectForward函數(shù)時傳入左值、右值、const左值、const右值,能夠匹配到對應(yīng)版本的Func函數(shù)。

但實(shí)際調(diào)用PerfectForward函數(shù)時傳入左值和右值,最終都匹配到了左值引用版本的Func函數(shù),調(diào)用PerfectForward函數(shù)時傳入const左值和const右值,最終都匹配到了const左值引用版本的Func函數(shù)。根本原因就是,右值被引用后會導(dǎo)致右值被存儲到特定位置,這時這個右值可以被取到地址,并且可以被修改,所以在PerfectForward函數(shù)中調(diào)用Func函數(shù)時會將t識別成左值。

也就是說,右值經(jīng)過一次參數(shù)傳遞后其屬性會退化成左值,如果想要在這個過程中保持右值的屬性,就需要用到完美轉(zhuǎn)發(fā)。

完美轉(zhuǎn)發(fā)保持值的屬性

要想在參數(shù)傳遞過程中保持其原有的屬性,需要在傳參時調(diào)用forward函數(shù)。比如:

templateclassT

voidPerfectForward(Tt)

Func(std::forwardT(t));

經(jīng)過完美轉(zhuǎn)發(fā)后,調(diào)用PerfectForward函數(shù)時傳入的是右值就會匹配到右值引用版本的Func函數(shù),傳入的是const右值就會匹配到const右值引用版本的Func函數(shù),這就是完美轉(zhuǎn)發(fā)的價值。

完美轉(zhuǎn)發(fā)的使用場景

下面模擬實(shí)現(xiàn)了一個簡化版的list類,類當(dāng)中分別提供了左值引用版本和右值引用版本的push_back和insert函數(shù)。

代碼如下:

namespacecl

templateclassT

structListNode

T_data;

ListNode*_next=nullptr;

ListNode*_prev=nullptr;

templateclassT

classlist

typedefListNodeTnode;

public:

//構(gòu)造函數(shù)

list()

_head=newnode;

_head-_next=_head;

_head-_prev=_head;

//左值引用版本的push_back

voidpush_back(constTx)

insert(_head,x);

//右值引用版本的push_back

voidpush_back(Tx)

insert(_head,std::forwardT(x));//完美轉(zhuǎn)發(fā)

//左值引用版本的insert

voidinsert(node*pos,constTx)

node*prev=pos-_prev;

node*newnode=newnode;

newnode-_data=x;

prev-_next=newnode;

newnode-_prev=prev;

newnode-_next=pos;

pos-_prev=newnode;

//右值引用版本的insert

voidinsert(node*pos,Tx)

node*prev=pos-_prev;

node*newnode=newnode;

newnode-_data=std::forwardT//完美轉(zhuǎn)發(fā)

prev-_next=newnode;

newnode-_prev=prev;

newnode-_next=pos;

pos-_prev=newnode;

private:

node*_head;//指向鏈表頭結(jié)點(diǎn)的指針

下面定義一個list對象,list容器中存儲的就是之前模擬實(shí)現(xiàn)的string類,這里分別傳入左值和右值調(diào)用不同版本的push_back。比如:

intmain()

cl::listcl::string

cl::strings("1111");

lt.push_back(s);//調(diào)用左值引用版

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論