久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

詳解C++右值引用

 waston 2018-07-16

C++0x標(biāo)準(zhǔn)出來(lái)很長(zhǎng)時(shí)間了,,引入了很多牛逼的特性[1],。其中一個(gè)便是右值引用,Thomas Becker的文章[2]很全面的介紹了這個(gè)特性,,讀后有如醍醐灌頂,,翻譯在此以便深入理解,。

概述

右值引用是由C++0x標(biāo)準(zhǔn)引入c++的一個(gè)令人難以捉摸的特性,。我曾偶爾聽到過(guò)有c++領(lǐng)域的大牛這么說(shuō):

每次我想抓住右值引用的時(shí)候,它總能從我手里跑掉,。

想把右值引用裝進(jìn)腦袋實(shí)在太難了,。

我不得不教別人右值引用,這太可怕了,。

右值引用惡心的地方在于,,當(dāng)你看到它的時(shí)候根本不知道它的存在有什么意義,它是用來(lái)解決什么問(wèn)題的,。所以我不會(huì)馬上介紹什么是右值引用,。更好的方式是從它將解決的問(wèn)題入手,然后講述右值引用是如何解決這些問(wèn)題的,。這樣,,右值引用的定義才會(huì)看起來(lái)合理和自然。

右值引用至少解決了這兩個(gè)問(wèn)題:

  1. 實(shí)現(xiàn)move語(yǔ)義
  2. 完美轉(zhuǎn)發(fā)(Perfect forwarding)

如果你不懂這兩個(gè)問(wèn)題,,別擔(dān)心,,后面會(huì)詳細(xì)地介紹。我們會(huì)從move語(yǔ)義開始,但在開始之前要首先讓你回憶起c++的左值和右值是什么,。關(guān)于左值和右值我很難給出一個(gè)嚴(yán)密的定義,不過(guò)下面的解釋已經(jīng)足以讓你明白什么是左值和右值,。

在c語(yǔ)言發(fā)展的較早時(shí)期,,左值和右值的定義是這樣的:左值是一個(gè)可以出現(xiàn)在賦值運(yùn)算符的左邊或者右邊的表達(dá)式e,而右值則是只能出現(xiàn)在右邊的表達(dá)式,。例如:

int a = 42;                                                
int b = 43;                                                

// a與b都是左值                              
a = b; // ok                                                
b = a; // ok                                                
a = a * b; // ok                                            

// a * b是右值:                                      
int c = a * b; // ok, 右值在等號(hào)右邊
a * b = 42; // 錯(cuò)誤,,右值在等號(hào)左邊

在c++中,我們?nèi)匀豢梢杂眠@個(gè)直觀的辦法來(lái)區(qū)分左值和右值,。不過(guò),,c++中的用戶自定義類型引入了關(guān)于可變性和可賦值性的微妙變化,這會(huì)讓這個(gè)方法變的不那么地正確,。我們沒(méi)有必要繼續(xù)深究下去,,這里還有另外一種定義可以讓你很好的處理關(guān)于右值的問(wèn)題:左值是一個(gè)指向某內(nèi)存空間的表達(dá)式,并且我們可以用&操作符獲得該內(nèi)存空間的地址,。右值就是非左值的表達(dá)式,。例如:

// 左值:                                                        
//                                                                
int i = 42;                                                        
i = 43; // ok, i是左值
int* p = &i; // ok, i是左值
int& foo();                                                        
foo() = 42; // ok, foo()是左值
int* p1 = &foo(); // ok, foo()是左值

// 右值:                                                        
//                                                                
int foobar();                                                      
int j = 0;                                                        
j = foobar(); // ok, foobar()是右值
int* p2 = &foobar(); // 錯(cuò)誤,不能取右值的地址
j = 42; // ok, 42是右值

如果你對(duì)左值和右值的嚴(yán)密的定義有興趣的話,,可以看下Mikael Kilpel?inen的文章[3],。

move語(yǔ)義

假設(shè)class X包含一個(gè)指向某資源的指針或句柄m_pResource。這里的資源指的是任何需要耗費(fèi)一定的時(shí)間去構(gòu)造,、復(fù)制和銷毀的東西,,比如說(shuō)以動(dòng)態(tài)數(shù)組的形式管理一系列的元素的std::vector。邏輯上而言X的賦值操作符應(yīng)該像下面這樣:

X& X::operator=(X const & rhs)
{
  // [...]
  // 銷毀m_pResource指向的資源
  // 復(fù)制rhs.m_pResource所指的資源,,并使m_pResource指向它
  // [...]
}

同樣X(jué)的拷貝構(gòu)造函數(shù)也是這樣,。假設(shè)我們這樣來(lái)用X:

X foo(); // foo是一個(gè)返回值為X的函數(shù)
X x;
x = foo();

最后一行有如下的操作:

  1. 銷毀x所持有的資源
  2. 復(fù)制foo返回的臨時(shí)對(duì)象所擁有的資源
  3. 銷毀臨時(shí)對(duì)象,釋放其資源

上面的過(guò)程是可行的,,但是更有效率的辦法是直接交換x和臨時(shí)對(duì)象中的資源指針,,然后讓臨時(shí)對(duì)象的析構(gòu)函數(shù)去銷毀x原來(lái)?yè)碛械馁Y源。換句話說(shuō),,當(dāng)賦值操作符的右邊是右值的時(shí)候,,我們希望賦值操作符被定義成下面這樣:

// [...]
// swap m_pResource and rhs.m_pResource
// [...]

這就是所謂的move語(yǔ)義。在之前的c++中,,這樣的行為是很難實(shí)現(xiàn)的,。雖然我也聽到有的人說(shuō)他們可以用模版元編程來(lái)實(shí)現(xiàn),但是我還從來(lái)沒(méi)有遇到過(guò)能給我解釋清楚如何具體實(shí)現(xiàn)的人,。所以這一定是相當(dāng)復(fù)雜的,。C++0x通過(guò)重載的辦法來(lái)實(shí)現(xiàn):

X& X::operator=(<mystery type> rhs)
{
  // [...]
  // swap this->m_pResource and rhs.m_pResource
  // [...]  
}

既然我們是要重載賦值運(yùn)算符,那么肯定是引用類型,。另外我們希望具有這樣的行為:現(xiàn)在有兩種重載,,一種參數(shù)是普通的引用,,另一種參數(shù)是,那么當(dāng)參數(shù)是個(gè)右值時(shí)就會(huì)選擇,,當(dāng)參數(shù)是左值是還是選擇普通的引用類型,。

把上面的換成右值引用,我們終于看到了右值引用的定義,。

右值引用

如果X是一種類型,,那么X&&就叫做X的右值引用。為了更好的區(qū)分兩,,普通引用現(xiàn)在被稱為左值引用,。

右值引用和左值引用的行為差不多,但是有幾點(diǎn)不同,,最重要的就是函數(shù)重載時(shí)左值使用左值引用的版本,,右值使用右值引用的版本:

void foo(X& x); // 左值引用重載
void foo(X&& x); // 右值引用重載

X x;
X foobar();

foo(x); // 參數(shù)是左值,調(diào)用foo(X&)
foo(foobar()); // 參數(shù)是右值,,調(diào)用foo(X&&)

重點(diǎn)在于:

右值引用允許函數(shù)在編譯期根據(jù)參數(shù)是左值還是右值來(lái)建立分支,。

理論上確實(shí)可以用這種方式重載任何函數(shù),但是絕大多數(shù)情況下這樣的重載只出現(xiàn)在拷貝構(gòu)造函數(shù)和賦值運(yùn)算符中,,以用來(lái)實(shí)現(xiàn)move語(yǔ)義:

X& X::operator=(X const & rhs); // classical implementation
X& X::operator=(X&& rhs)
{
  // Move semantics: exchange content between this and rhs
  return *this;
}

實(shí)現(xiàn)針對(duì)右值引用重載的拷貝構(gòu)造函數(shù)與上面類似,。

如果你實(shí)現(xiàn)了void foo(X&);,但是沒(méi)有實(shí)現(xiàn)void foo(X&&);,,那么和以前一樣foo的參數(shù)只能是左值,。如果實(shí)現(xiàn)了void foo(X const &);,但是沒(méi)有實(shí)現(xiàn)void foo(X&&);,,仍和以前一樣,,foo的參數(shù)既可以是左值也可以是右值。唯一能夠區(qū)分左值和右值的辦法就是實(shí)現(xiàn)void foo(X&&);,。最后,,如果只實(shí)現(xiàn)了實(shí)現(xiàn)void foo(X&&);,但卻沒(méi)有實(shí)現(xiàn)void foo(X&);void foo(X const &);,,那么foo的參數(shù)將只能是右值,。

強(qiáng)制move語(yǔ)義

c++的第一版修正案里有這樣一句話:“C++標(biāo)準(zhǔn)委員會(huì)不應(yīng)該制定一條阻止程序員拿起槍朝自己的腳丫子開火的規(guī)則?!眹?yán)肅點(diǎn)說(shuō)就是c++應(yīng)該給程序員更多控制的權(quán)利,,而不是擅自糾正他們的疏忽。于是,,按照這種思想,,C++0x中既可以在右值上使用move語(yǔ)義,也可以在左值上使用,標(biāo)準(zhǔn)程序庫(kù)中的函數(shù)swap就是一個(gè)很好的例子,。這里假設(shè)X就是前面我們已經(jīng)重載右值引用以實(shí)現(xiàn)move語(yǔ)義的那個(gè)類,。

template<class T>
void swap(T& a, T& b)
{
  T tmp(a);
  a = b;
  b = tmp;
}

X a, b;
swap(a, b);

上面的代碼中沒(méi)有右值,所以沒(méi)有使用move語(yǔ)義,。但move語(yǔ)義用在這里最合適不過(guò)了:當(dāng)一個(gè)變量(a)作為拷貝構(gòu)造函數(shù)或者賦值的來(lái)源時(shí),,這個(gè)變量要么就是以后都不會(huì)再使用,要么就是作為賦值操作的目標(biāo)(a = b),。

C++11中的標(biāo)準(zhǔn)庫(kù)函數(shù)std::move可以解決我們的問(wèn)題。這個(gè)函數(shù)只會(huì)做一件事:把它的參數(shù)轉(zhuǎn)換為一個(gè)右值并且返回,。C++11中的swap函數(shù)是這樣的:

template<class T>
void swap(T& a, T& b)
{
  T tmp(std::move(a));
  a = std::move(b);
  b = std::move(tmp);
}

X a, b;
swap(a, b);

現(xiàn)在的swap使用了move語(yǔ)義,。值得注意的是對(duì)那些沒(méi)有實(shí)現(xiàn)move語(yǔ)義的類型來(lái)說(shuō)(沒(méi)有針對(duì)右值引用重載拷貝構(gòu)造函數(shù)和賦值操作符),新的swap仍然和舊的一樣,。

std::move是個(gè)很簡(jiǎn)單的函數(shù),,不過(guò)現(xiàn)在我還不能將它的實(shí)現(xiàn)展現(xiàn)給你,后面再詳細(xì)說(shuō)明,。

像上面的swap函數(shù)一樣,,盡可能的使用std::move會(huì)給我們帶來(lái)以下好處:

  • 對(duì)那些實(shí)現(xiàn)了move語(yǔ)義的類型來(lái)說(shuō),許多標(biāo)準(zhǔn)庫(kù)算法和操作會(huì)得到很大的性能上的提升,。例如就地排序:就地排序算法基本上只是在交換容器內(nèi)的對(duì)象,,借助move語(yǔ)義的實(shí)現(xiàn),交換操作會(huì)快很多,。
  • stl通常對(duì)某種類型的可復(fù)制性有一定的要求,,比如要放入容器的類型。其實(shí)仔細(xì)研究下,,大多數(shù)情況下只要有可移動(dòng)性就足夠了,。所以我們可以在一些之前不可復(fù)制的類型不被允許的情況下,用一些不可復(fù)制但是可以移動(dòng)的類型(unique_ptr),。這樣的類型是可以作為容器元素的,。

右值引用是右值嗎?

假設(shè)有以下代碼:

void foo(X&& x)
{
  X anotherX = x;
  // ...
}

現(xiàn)在考慮一個(gè)有趣的問(wèn)題:在foo函數(shù)內(nèi),,哪個(gè)版本的X拷貝構(gòu)造函數(shù)會(huì)被調(diào)用呢,?這里的x是右值引用類型。把x也當(dāng)作右值來(lái)處理看起來(lái)貌似是正確的,,也就是調(diào)用這個(gè)拷貝構(gòu)造函數(shù):

X(X&& rhs);

有些人可能會(huì)認(rèn)為一個(gè)右值引用本身就是右值,。但右值引用的設(shè)計(jì)者們采用了一個(gè)更微妙的標(biāo)準(zhǔn):

右值引用類型既可以被當(dāng)作左值也可以被當(dāng)作右值,判斷的標(biāo)準(zhǔn)是,,如果它有名字,,那就是左值,否則就是右值。

在上面的例子中,,因?yàn)橛抑狄脁是有名字的,,所以x被當(dāng)作左值來(lái)處理。

void foo(X&&void foo(X&& x)
{ X anotherX = x; // 調(diào)用X(X const & rhs) }

下面是一個(gè)沒(méi)有名字的右值引用被當(dāng)作右值處理的例子:

X&& goo();
X x = goo(); // 調(diào)用X(X&& rhs),,goo的返回值沒(méi)有名字

之所以采用這樣的判斷方法,,是因?yàn)椋喝绻试S悄悄地把move語(yǔ)義應(yīng)用到有名字的東西(比如foo中的x)上面,代碼會(huì)變得容易出錯(cuò)和讓人迷惑,。

void foo(X&& x)
{
  X anotherX = x;
  // x仍然在作用域內(nèi)
}

這里的x仍然是可以被后面的代碼所訪問(wèn)到的,,如果把x作為右值看待,那么經(jīng)過(guò)X anotherX = x;后,,x的內(nèi)容已經(jīng)發(fā)生變化,。move語(yǔ)義的重點(diǎn)在于將其應(yīng)用于那些不重要的東西上面,那些move之后會(huì)馬上銷毀而不會(huì)被再次用到的東西上面,。所以就有了上面的準(zhǔn)則:如果有名字,,那么它就是左值。

那另外一半,,“如果沒(méi)有名字,,那它就是右值”又如何理解呢?上面goo()的例子中,,理論上來(lái)說(shuō)goo()所引用的對(duì)象也可能在X x = goo();后被訪問(wèn)的到,。但是回想一下,這種行為不正是我們想要的嗎,?我們也想隨心所欲的在左值上面使用move語(yǔ)義,。正是“如果沒(méi)有名字,那它就是右值”的規(guī)則讓我們能夠?qū)崿F(xiàn)強(qiáng)制move語(yǔ)義,。其實(shí)這就是std::move的原理,。這里展示std::move的具體實(shí)現(xiàn)還是太早了點(diǎn),不過(guò)我們離理解std::move更近了一步,。它什么都沒(méi)做,,只是把它的參數(shù)通過(guò)右值引用的形式傳遞下去。

std::move(x)的類型是右值引用,,而且它也沒(méi)有名字,,所以它是個(gè)右值。因此std::move(x)正是通過(guò)隱藏名字的方式把它的參數(shù)變?yōu)橛抑怠?/p>

下面這個(gè)例子將展示記住“如果它有名字”的規(guī)則是多么重要,。假設(shè)你寫了一個(gè)類Base,,并且通過(guò)重載拷貝構(gòu)造函數(shù)和賦值操作符實(shí)現(xiàn)了move語(yǔ)義:

Base(Base const & rhs); // non-move semantics
Base(Base&& rhs); // move semantics

然后又寫了一個(gè)繼承自Base的類Derived。為了保證Derived對(duì)象中的Base部分能夠正確實(shí)現(xiàn)move語(yǔ)義,,必須也重載Derived類的拷貝構(gòu)造函數(shù)和賦值操作符,。先讓我們看下拷貝構(gòu)造函數(shù)(賦值操作符的實(shí)現(xiàn)類似),,左值版本的拷貝構(gòu)造函數(shù)很直白:

Derived(Derived const & rhs)
  : Base(rhs)
{
  // Derived-specific stuff
}

但右值版本的重載卻要仔細(xì)研究下,下面是某個(gè)不知道“如果它有名字”規(guī)則的程序員寫的:

Derived(Derived&& rhs)
  : Base(rhs) // 錯(cuò)誤:rhs是個(gè)左值
{
  // ...
}

如果像上面這樣寫,,調(diào)用的永遠(yuǎn)是Base的非move語(yǔ)義的拷貝構(gòu)造函數(shù),。因?yàn)閞hs有名字,所以它是個(gè)左值,。但我們想要調(diào)用的卻是move語(yǔ)義的拷貝構(gòu)造函數(shù),,所以應(yīng)該這么寫:

Derived(Derived&& rhs)
  : Base(std::move(rhs)) // good, calls Base(Base&& rhs)
{
  // Derived-specific stuff
}

move語(yǔ)義與編譯器優(yōu)化

現(xiàn)在有這么一個(gè)函數(shù):

X foo()
{
  X x;
  // perhaps do something to x
  return x;
}

一看到這個(gè)函數(shù),你可能會(huì)說(shuō),,咦,,這個(gè)函數(shù)里有一個(gè)復(fù)制的動(dòng)作,不如讓它使用move語(yǔ)義:

X foo()
{
  X x;
  // perhaps do something to x
  return std::move(x); // making it worse!
}

很不幸的是,,這樣不但沒(méi)有幫助反而會(huì)讓它變的更糟?,F(xiàn)在的編譯器基本上都會(huì)做返回值優(yōu)化(return value optimization)。也就是說(shuō),,編譯器會(huì)在函數(shù)返回的地方直接創(chuàng)建對(duì)象,而不是在函數(shù)中創(chuàng)建后再?gòu)?fù)制出來(lái),。很明顯,,這比move語(yǔ)義還要好一點(diǎn)。

所以,,為了更好的使用右值引用和move語(yǔ)義,,你得很好的理解現(xiàn)在編譯器的一些特殊效果,比如return value optimization和copy elision,。并且在運(yùn)用右值引用和move語(yǔ)義時(shí)將其考慮在內(nèi),。Dave Abrahams就這一主題寫了一系列的文章[4]。

完美轉(zhuǎn)發(fā):?jiǎn)栴}

除了實(shí)現(xiàn)move語(yǔ)義之外,,右值引用要解決的另一個(gè)問(wèn)題就是完美轉(zhuǎn)發(fā)問(wèn)題(perfect forwarding),。假設(shè)有下面這樣一個(gè)工廠函數(shù):

template<typename T, typename Arg>
shared_ptr<T> factory(Arg arg)
{
  return shared_ptr<T>(new T(arg));
}

很明顯,這個(gè)函數(shù)的意圖是想把參數(shù)arg轉(zhuǎn)發(fā)給T的構(gòu)造函數(shù),。對(duì)參數(shù)arg而言,,理想的情況是好像factory函數(shù)不存在一樣,直接調(diào)用構(gòu)造函數(shù),,這就是所謂的“完美轉(zhuǎn)發(fā)”,。但真實(shí)情況是這個(gè)函數(shù)是錯(cuò)誤的,因?yàn)樗肓祟~外的通過(guò)值的函數(shù)調(diào)用,,這將不適用于那些以引用為參數(shù)的構(gòu)造函數(shù),。

最常見的解決方法,比如被boost::bind采用的,,就是讓外面的函數(shù)以引用作為參數(shù),。

template<typename T, typename Arg>
shared_ptr<T> factory(Arg& arg)
{
  return shared_ptr<T>(new T(arg));
}

這樣確實(shí)會(huì)好一點(diǎn),,但不是完美的。現(xiàn)在的問(wèn)題是這個(gè)函數(shù)不能接受右值作為參數(shù):

factory<X>(hoo()); // error if hoo returns by value
factory<X>(41); // error

這個(gè)問(wèn)題可以通過(guò)一個(gè)接受const引用的重載解決:

template<typename T, typename Arg>
shared_ptr<T> factory(Arg const & arg)
{
  return shared_ptr<T>(new T(arg));
}

這個(gè)辦法仍然有兩個(gè)問(wèn)題,。首先如果factory函數(shù)的參數(shù)不是一個(gè)而是多個(gè),,那就需要針對(duì)每個(gè)參數(shù)都要寫const引用和non-const引用的重載。代碼會(huì)變的出奇的長(zhǎng),。

其次這種辦法也稱不上是完美轉(zhuǎn)發(fā),,因?yàn)樗荒軐?shí)現(xiàn)move語(yǔ)義。factory內(nèi)的構(gòu)造函數(shù)的參數(shù)是個(gè)左值(因?yàn)樗忻郑?,所以即使?gòu)造函數(shù)本身已經(jīng)支持,,factory也無(wú)法實(shí)現(xiàn)move語(yǔ)義。

右值引用可以很好的解決上面這些問(wèn)題,。它使得不通過(guò)重載而實(shí)現(xiàn)真正的完美轉(zhuǎn)發(fā)成為可能,。為了弄清楚是如何實(shí)現(xiàn)的,我們還需要再掌握兩個(gè)右值引用的規(guī)則,。

完美轉(zhuǎn)發(fā):解決方案

第一條右值引用的規(guī)則也會(huì)影響到左值引用,。回想一下,,在c++11標(biāo)準(zhǔn)之前,,是不允許出現(xiàn)對(duì)某個(gè)引用的引用的:像A& &這樣的語(yǔ)句會(huì)導(dǎo)致編譯錯(cuò)誤。不同的是,,在c++11標(biāo)準(zhǔn)里面引入了引用疊加規(guī)則:

A& & => A&
A& && => A&
A&& & => A&
A&& && => A&&

另外一個(gè)是模版參數(shù)推導(dǎo)規(guī)則,。這里的模版是接受一個(gè)右值引用作為模版參數(shù)的函數(shù)模版。

template<typename T>
void foo(T&&);

針對(duì)這樣的模版有如下的規(guī)則:

  1. 當(dāng)函數(shù)foo的實(shí)參是一個(gè)A類型的左值時(shí),,T的類型是A&,。再根據(jù)引用疊加規(guī)則判斷,最后參數(shù)的實(shí)際類型是A&,。
  2. 當(dāng)foo的實(shí)參是一個(gè)A類型的右值時(shí),,T的類型是A。根據(jù)引用疊加規(guī)則可以判斷,,最后的類型是A&&,。

有了上面這些規(guī)則,我們可以用右值引用來(lái)解決前面的完美轉(zhuǎn)發(fā)問(wèn)題,。下面是解決的辦法:

template<typename T, typename Arg>
shared_ptr<T> factory(Arg&& arg)
{
  return shared_ptr<T>(new T(std::forward<Arg>(arg)));
}

std::forward的定義如下:

template<class S>
S&& forward(typename remove_reference<S>::type& a) noexcept
{
  return static_cast<S&&>(a);
}

上面的程序是如何解決完美轉(zhuǎn)發(fā)的問(wèn)題的,?我們需要討論當(dāng)factory的參數(shù)是左值或右值這兩種情況。假設(shè)A和X是兩種類型,。先來(lái)看factory的參數(shù)是X類型的左值時(shí)的情況:

X x;
factory<A>(x);

根據(jù)上面的規(guī)則可以推導(dǎo)得到,,factory的模版參數(shù)Arg變成了X&,于是編譯器會(huì)像下面這樣將模版實(shí)例化:

shared_ptr<A> factory(X& && arg)
{
  return shared_ptr<A>(new A(std::forward<X&>(arg)));
}

X& && forward(remove_reference<X&>::type& a) noexcept
{
  return static_cast<X& &&>(a);
}

應(yīng)用前面的引用疊加規(guī)則并且求得remove_reference的值后,,上面的代碼又變成了這樣:

shared_ptr<A> factory(X& arg)
{
  return shared_ptr<A>(new A(std::forward<X&>(arg)));
}

X& std::forward(X& a)
{
  return static_cast<X&>(a);
}

這對(duì)于左值來(lái)說(shuō)當(dāng)然是完美轉(zhuǎn)發(fā):通過(guò)兩次中轉(zhuǎn),,參數(shù)arg被傳遞給了A的構(gòu)造函數(shù),,這兩次中轉(zhuǎn)都是通過(guò)左值引用完成的。

現(xiàn)在再考慮參數(shù)是右值的情況:

X foo();
factory<A>(foo());

再次根據(jù)上面的規(guī)則推導(dǎo)得到:

shared_ptr<A> factory(X&& arg)
{
  return shared_ptr<A>(new A(std::forward<X>(arg)));
}

X&& forward(X& a) noexcept
{
  return static_cast<X&&>(a);
}

對(duì)右值來(lái)說(shuō),,這也是完美轉(zhuǎn)發(fā):參數(shù)通過(guò)兩次中轉(zhuǎn)被傳遞給A的構(gòu)造函數(shù),。另外對(duì)A的構(gòu)造函數(shù)來(lái)說(shuō),它的參數(shù)是個(gè)被聲明為右值引用類型的表達(dá)式,,并且它還沒(méi)有名字,。那么根據(jù)第5節(jié)中的規(guī)則可以判斷,它就是個(gè)右值,。這意味著這樣的轉(zhuǎn)發(fā)完好的保留了move語(yǔ)義,,就像factory函數(shù)并不存在一樣。

事實(shí)上std::forward的真正目的在于保留move語(yǔ)義,。如果沒(méi)有std::forward,,一切都是正常的,但有一點(diǎn)除外:A的構(gòu)造函數(shù)的參數(shù)是有名字的,,那這個(gè)參數(shù)就只能是個(gè)左值,。

如果你想再深入挖掘一點(diǎn)的話,不妨問(wèn)下自己這個(gè)問(wèn)題:為什么需要remove_reference,?答案是其實(shí)根本不需要,。如果把remove_reference<S>::type&換成S&,一樣可以得出和上面相同的結(jié)論,。但是這一切的前提是我們指定Arg作為std::forward的模版參數(shù)。remove_reference存在的原因就是強(qiáng)迫我們?nèi)ミ@樣做,。

已經(jīng)講的差不多了,,剩下的就是std::move的實(shí)現(xiàn)了。記住,,std::move的用意在于將它的參數(shù)傳遞下去,,將它轉(zhuǎn)換成右值。

template<class T>
typename remove_reference<T>::type&&
std::move(T&& a) noexcept
{
  typedef typename remove_reference<T>::type&& RvalRef;
  return static_cast<RvalRef>(a);
}

下面假設(shè)我們針對(duì)一個(gè)X類型的左值調(diào)用std::move,。

X x;
std::move(x);

根據(jù)前面的模版參數(shù)推導(dǎo)規(guī)則,,模版參數(shù)T變成了X&,于是:

typename remove_reference<X&>::type&&
std::move(X& && a) noexcept
{
  typedef typename remove_reference<X&>::type&& RvalRef;
  return static_cast<RvalRef>(a);
}

然后求得remove_reference的值,,并應(yīng)用引用疊加規(guī)則,,得到:

X&& std::move(X& a) noexcept
{
  return static_cast<X&&>(a);
}

這就可以了,x變成了沒(méi)有名字的右值引用,。

參數(shù)是右值的情況由你來(lái)自己推導(dǎo),。不過(guò)你可能馬上就想跳過(guò)去了,為什么會(huì)有人把std::move用在右值上呢,?它的功能不就是把參數(shù)變成右值么,。另外你可能也注意到了,,我們完全可以用static_cast<X&&>(x)來(lái)代替std::move(x),不過(guò)大多數(shù)情況下還是用std::move(x)比較好,。

參考

  1. C++11 from wikipedia
  2. C++ Rvalue References Explained
  3. Lvalues and Rvalues
  4. RValue References: Moving Forward?
  5. A Brief Introduction to Rvalue References
  6. C++11 標(biāo)準(zhǔn)新特性: 右值引用與轉(zhuǎn)移語(yǔ)義
  7. C++11 完美轉(zhuǎn)發(fā)
  8. 《C++0x漫談》系列之:右值引用(或“move語(yǔ)意與完美轉(zhuǎn)發(fā)”)(下)

本文轉(zhuǎn)自:[譯]詳解C++右值引用

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多