右值引用的概念有些讀者可能會感到陌生,其實他和c++98/03中的左值引用有些類似,例如,c++98/03中的左值引用是這樣的:
int i = 0;這裡的int&是對左值進行繫結(但是int&卻不能繫結右值),相應的,對右值進行繫結的引用就是右值引用,他的語法是這樣的a&&,通過雙引號來表示繫結型別為a的右值。通過&&我們就可以很方便的繫結右值了,比如我們可以這樣繫結乙個右值:int& j = i;
int& i = 0;這裡我們繫結了乙個右值0,關於右值的概念會在後面介紹。右值引用是c++11中新增加的乙個很重要的特性,他主是要用來解決c++98/03中遇到的兩個問題,第乙個問題就是臨時物件非必要的昂貴的拷貝操作,第二個問題是在模板函式中如何按照引數的實際型別進行**。通過引入右值引用,很好的解決了這兩個問題,改進了程式效能,後面將會詳細介紹右值引用是如何解決這兩個問題的。
和右值引用相關的概念比較多,比如:右值、純右值、將亡值、universal references、引用摺疊、移動語義、move語義和完美**等等。很多都是新概念,對於剛學習c++11右值引用的初學者來說,可能會覺得右值引用過於複雜,概念之間的關係難以理清。
右值引用實際上並沒有那麼複雜,其實是關於4行**的故事,通過簡單的4行**我們就能清晰的理解右值引用相關的概念了。本文希望帶領讀者通過4行**來理解右值引用相關的概念,理清他們之間的關係,並最終能透徹地掌握c++11的特性—右值引用。
如果想一起交流的可以加這個群:941636044 ,有什麼問題可以群裡面交流,群裡面也有一些方便學習c語言c++程式設計的資料可以給你利用哦!
int i = getvar();上面的這行**很簡單,從getvar()函式獲取乙個整形值,然而,這行**會產生幾種型別的值呢?答案是會產生兩種型別的值,一種是左值i,一種是函式getvar()返回的臨時值,這個臨時值在表示式結束後就銷毀了,而左值i在表示式結束後仍然存在,這個臨時值就是右值,具體來說是乙個純右值,右值是不具名的。區分左值和右值的乙個簡單辦法是:看能不能對表示式取位址,如果能,則為左值,否則為右值。
所有的具名變數或物件都是左值,而匿名變數則是右值,比如,簡單的賦值語句:
int i = 0;在這條語句中,i 是左值,0 是字面量,就是右值。在上面的**中,i 可以被引用,0 就不可以了。具體來說上面的表示式中等號右邊的0是純右值(prvalue),在c++11中所有的值必屬於左值、將亡值、純右值三者之一。比如,非引用返回的臨時變數、運算表示式產生的臨時變數、原始字面量和lambda表示式等都是純右值。而將亡值是c++11新增的、與右值引用相關的表示式,比如,將要被移動的物件、t&&函式返回值、std::move返回值和轉換為t&&的型別的轉換函式的返回值等。關於將亡值我們會在後面介紹,先看下面的**:
int j = 5;上面的**中5是乙個原始字面量,是乙個lambda表示式,都是屬於純右值,他們的特點是在表示式結束之後就銷毀了。auto f = ;
通過地行**我們對右值有了乙個初步的認識,知道了什麼是右值,接下來再來看看第二行**。
t&& k = getvar();第二行**和第一行**很像,只是相比第一行**多了「&&」,他就是右值引用,我們知道左值引用是對左值的引用,那麼,對應的,對右值的引用就是右值引用,而且右值是匿名變數,我們也只能通過引用的方式來獲取右值。雖然第二行**和第一行**看起來差別不大,但是實際上語義的差別很大,這裡,getvar()產生的臨時值不會像第一行**那樣,在表示式結束之後就銷毀了,而是會被「續命」,他的生命週期將會通過右值引用得以延續,和變數k的宣告週期一樣長。
右值引用的第乙個特點
通過右值引用的宣告,右值又「重獲新生」,其生命週期與右值引用型別變數的生命週期一樣長,只要該變數還活著,該右值臨時量將會一直存活下去。讓我們通過乙個簡單的例子來看看右值的生命週期。如**清單1-1所示。
**清單1-1
#include這行**實際上來自於乙個類的建構函式,建構函式的乙個引數是乙個右值引用,為什麼將右值引用作為建構函式的引數呢?在解答這個問題之前我們先看乙個例子。如**清單1-2所示。using namespace std;
int g_constructcount=0;
int g_copyconstructcount=0;
int g_destructcount=0;
struct a
a()
**清單1-2
class ac++11之前呼叫模板函式時,存在乙個比較頭疼的問題,如何正確的傳遞引數。比如:public:
a():m_ptr(new int(0))
a(const a& a):m_ptr(new int(*a.m_ptr)) //深拷貝的拷貝建構函式
cout << "copy construct" << endl;
~a()
private:
int* m_ptr;
int main()
a(const a& a):m_ptr(new int(*a.m_ptr)) //深拷貝的拷貝建構函式
cout << "copy construct" << endl;
a(a&& a) :m_ptr(a.m_ptr)
a.m_ptr = nullptr;
cout << "move construct" << endl;
~a()
private:
int* m_ptr;
int main()
template都不能按照引數的本來的型別進行**。void forwardvalue(t& val)
processvalue(val); //右值引數會變成左值
template
void forwardvalue(const t& val)
processvalue(val); //引數都變成常量左值引用了
c++11引入了完美**:在函式模板中,完全依照模板的引數的型別(即保持引數的左值、右值特徵),將引數傳遞給函式模板中呼叫的另外乙個函式。c++11中的std::forward正是做這個事情的,他會按照引數的實際型別進行**。看下面的例子:
void processvalue(int& a)輸出:void processvalue(int&& a)
template
void forwardvalue(t&& val)
processvalue(std::forward(val)); //照引數本來的型別進行**。
void testdelcl()
int i = 0;
forwardvalue(i); //傳入左值
forwardvalue(0);//傳入右值
lvaue右值引用t&&是乙個universal references,可以接受左值或者右值,正是這個特性讓他適合作為乙個引數的路由,然後再通過std::forward按照引數的實際型別去匹配對應的過載函式,最終實現完美**。rvalue
我們可以結合完美**和移動語義來實現乙個泛型的工廠函式,這個工廠函式可以建立所有型別的物件。具體實現如下:
template這個工廠函式的引數是右值引用型別,內部使用std::forward按照引數的實際型別進行**,如果引數的實際型別是右值,那麼建立的時候會自動匹配移動構造,如果是左值則會匹配拷貝構造。t* instance(args&&… args)
return new t(std::forward(args)…);
如果想一起交流的可以加這個群:941636044 ,有什麼問題可以群裡面交流,群裡面也有一些方便學習c語言c++程式設計的資料可以給你利用哦!
通過4行**我們知道了什麼是右值和右值引用,以及右值引用的一些特點,利用這些特點我們才方便實現移動語義和完美**。c++11正是通過引入右值引用來優化效能,具體來說是通過移動語義來避免無謂拷貝的問題,通過move語義來將臨時生成的左值中的資源無代價的轉移到另外乙個物件中去,通過完美**來解決不能按照引數實際型別來**的問題(同時,完美**獲得的乙個好處是可以實現移動語義)。
c 11 右值引用
右值引用 是一種復合型別,跟c 的傳統引用很類似。為更準確地區分兩種型別,我們把傳統的c 引用稱為 左值引用 而使用 引用 這一術語時,我們的意思同時包含兩種引用 左值引用和右值引用。右值引用的行為跟左值引用類似,不同之處在於 右值引用可以繫結到臨時量 右值 而 非const的 左值引用卻不能繫結到...
C 11 右值引用
消除兩個物件互動時不必要的物件拷貝,節省運算儲存資源,提高效率。能夠更簡潔明確地定義泛型函式。1.右值引用 int a a 1 here,a is an lvalue 上述的a就是乙個左值。c 11中左值的宣告符號為 為了和左值區分,右值的宣告符號為 printreference const str...
C 11右值引用
c 11中引入的乙個非常重要的概念就是右值引用。理解右值引用是學習 移動語義 move semantics 的基礎。而要理解右值引用,就必須先區分左值與右值。對左值和右值的乙個最常見的誤解是 等號左邊的就是左值,等號右邊的就是右值。左值和右值都是針對表示式而言的,左值是指表示式結束後依然存在的持久物...