C 11 中的右值引用

2021-08-13 03:57:03 字數 3971 閱讀 5823

右值引用的功能

首先,我並不介紹什麼是右值引用,而是以乙個例子裡來介紹一下右值引用的功能:

#include

#include

usingnamespacestd;

classobj

obj(constobj& other)

};vectorfoo()

intmain()

首先我們編譯一下這個函式,執行結果如下:

tianfang > g++ main.cpp

tianfang > a.out

>> create obj 

>> copy create obj 

---- exit foo ----

>> copy create obj 

tianfang >

可以看到,對obj物件執行了兩次構造。vector是乙個常用的容器了,我們可以很容易的分析這這兩次拷貝構造的時機:

foo函式第二行,呼叫push_back的時候,會在vector裡建立乙個obj的副本

main函式第二行,執行複製函式的時候,會把foo()返回的物件全部複製過來,再次執行一次拷貝構造

由於物件的拷貝構造的開銷是非常大的,因此我們想就可能避免他們。其中,第一次拷貝構造是vector的特性所決定的,不可避免。但第二次拷貝構造,在c++ 11中就是可以避免的了。

tianfang > g++ -std=c++11main.cpp

tianfang > a.out

>> create obj 

>> copy create obj 

---- exit foo ----

tianfang >

可以看到,我們除了加上了乙個-std=c++11選項外,什麼都沒乾,但現在就把第二次的拷貝構造給去掉了。它是如何實現這一過程的呢?

在老版本中,當我們執行第二行的賦值操作的時候,執行過程如下:

foo()函式返回乙個臨時物件(這裡用~tmp來標識它)

執行vector的 '=' 函式,將物件k中的現有成員刪除,將~tmp的成員複製到k中來

刪除臨時物件~tmp

在c++11的版本中,執行過程如下:

foo()函式返回乙個臨時物件(這裡用~tmp來標識它)

執行vector的 '=' 函式,將物件k中的成員~tmp的成員互換,此時k中的成員就被替換成了~tmp中的成員。

刪除臨時物件~tmp(此時就刪除了以前的k中的成員)

關鍵的過程就是第2步,它不是複製而是交換,從而避免的成員的拷貝,但效果卻是一樣的。不用修改**,效能卻得到了提公升,對於程式設計師來說就是乙份免費的午餐。

但是,這份免費的午餐也不是無條件就可以獲取的,帶上-std=c++11編譯時,如果使用stl**可以享用這份午餐,但如果使用我們以前的老**發現還是和以前的功能是一樣的,那麼,如何讓我們以前的**也能得到這個效率的提公升呢?

通過交換減少資料的拷貝

為了演示如何在我們的**中也獲取這個效能提公升,首先我先寫了乙個山寨的vector:

#include

#include

usingnamespacestd;

classobj

obj(constobj& other)

};template

classcontainer

;~container()  

container(constcontainer& other)

constcontainer& operator= (constcontainer& other)

voidpush_back(constt& item)

};containerfoo()

intmain()

這個vector只能容納乙個元素,但並不妨礙我們的演示,其功能和前面的例子是一樣的,執行這段**,結果如下:

tianfang > make

g++ -std=c++11main.cpp

tianfang > a.out

>> create obj 

>> copy create obj 

---- exit foo ----

>> copy create obj 

tianfang >

如前所述,仍然有兩次拷貝構造。其實前面已經說過交換實現減少拷貝構造的原理,那麼,我們可以通過修改 '=' 函式來手動實現這一過程。

constcontainer& operator = (container& other)

在vc中執行這段**,發現執行結果和預期一致,

>> create obj 

>> copy create obj 

---- exit foo ----

但是,gcc中卻無法通過編譯,原因很簡單:gcc期望的賦值函式的引數是const型的,而這裡為了交換成員,而不能使用const型。

那麼,雖然gcc中不能生效,是否可以說在vc中就可以以這種形式獲取效能提公升呢?答案是否定的。雖然在這段**中這麼寫沒有問題,但賦值函式本身是期望複製功能的,而不是交換。例如,修改後下面的執行結果就不對了。

intmain()

gcc的告警是有道理的:如果 '=' 函式實現的是複製功能,雖然效率低點,但保證了功能正確,但如果實現的是交換的功能,則不能保證功能一定正確。只有當 '=' 函式右邊的物件為乙個臨時變數的時候,由於臨時變數會馬上被刪除掉,此時的交換和複製的效果是一樣的。其實vc也應該把這個告警加上才合適。

ps:對臨時變數定義和**不清楚的朋友可以參考一下這篇文章。

現在的問題是:我們無法在賦值函式裡區分傳入的是乙個臨時物件還是非臨時物件,因此只能執行複製操作。為了解決這一問題,c++中引入了乙個新的賦值函式的過載形式:

container& operator = (container&& other)

這個賦值函式通常稱為移動賦值函式,和老版本的相比,它有兩點區別:

入參不是const型,因此它是可以更改入參的值的,從而實現交換操作

入參前面有兩個&號,這個是c++11引入的新語法,稱為右值引用,它的使用方式和普通引用是一樣的,唯一的區別是可以指向臨時變數。

現在,我們就有兩個版本的賦值函式了,c++11在語法級別也做了適應:

現在,我們實現一下山寨版的移動賦值函式:

container& operator = (container&& other)

執行後結果就和我們期望的那樣,避免了成員的第二次的拷貝構造。

和移動賦值函式相應的,也有乙個乙個移動建構函式,也最好實現以下:

container (container&& other)

我們也可以實現自己的右值引用版的過載函式,這裡就不多介紹了。

注意:本文所示的**只是為了演示和實現右值引用,力求簡潔,並沒有寫得很完善(乙個典型的缺失就是在賦值函式中沒有判斷入參是否是本身),請不要將其應用於專案中。

完善的版本請看msdn文章:如何編寫乙個移動建構函式,其相應的對右值引用的介紹文章rvalue引用宣告:&&也非常值得一讀。

通過std::move函式顯式使用交換

首先看一下這段**:

classbigobj

bigobj(constbigobj& other)

bigobj(bigobj&& other)

};intmain()

}執行的時候就會發現:雖然我們定義了移動建構函式,但是它仍然會執行拷貝建構函式。這是因為編譯器並不認為obj是臨時變數。關於什麼變數才是臨時變數,前文已經給了個鏈結來說明它,簡單的說,我們能夠看到的命名變數都不是臨時變數。

雖然obj物件不是語言級別的臨時變數,但是從功能上來看,它就是乙個臨時變數,是可以使用移動建構函式來消除拷貝帶來的效能損失的。為了解決這一問題,c++提供了乙個move函式來把obj變數強制轉換為右值引用,這樣就可以使用移動建構函式了。

for(inti = 0; i < 3; i++)

不過,需要注意的是,和系統識別的臨時變數而自動使用右值引用不同,這種強制轉換是有一定的風險的,由於在push_back後執行了交換操作,如果再次使用它會出現非預期的結果,只有能確定該變數不會再次被使用才能執行這種轉換。

C11中的右值引用

二 參考鏈結 左值是指表示式結束後依然存在的持久物件,右值是指表示式結束時就不再存在的臨時物件。看能不能對表示式取位址,如果能,則為左值,否則為右值。所有的具名變數或物件都是左值,而右值不具名。捨棄 c 語言中的左值和右值概念 乙個是純右值 prvalue,purervalue 比如,非引用返回的臨...

c 11 右值引用

右值引用 是一種復合型別,跟c 的傳統引用很類似。為更準確地區分兩種型別,我們把傳統的c 引用稱為 左值引用 而使用 引用 這一術語時,我們的意思同時包含兩種引用 左值引用和右值引用。右值引用的行為跟左值引用類似,不同之處在於 右值引用可以繫結到臨時量 右值 而 非const的 左值引用卻不能繫結到...

C 11 右值引用

消除兩個物件互動時不必要的物件拷貝,節省運算儲存資源,提高效率。能夠更簡潔明確地定義泛型函式。1.右值引用 int a a 1 here,a is an lvalue 上述的a就是乙個左值。c 11中左值的宣告符號為 為了和左值區分,右值的宣告符號為 printreference const str...