C++ 11 中的右值引用
C++ 11 中的右值引用
右值引用的功能
首先,我并不介紹什么是右值引用,而是以一個(gè)例子里來介紹一下右值引用的功能:
????#include?<iostream>
????#include?<vector>
????using?namespace?std;
????class?obj
????{
????public?:
????????obj() { cout <<?">> create obj "?<< endl; }
????????obj(const?obj& other) { cout <<?">> copy create obj "?<< endl; }
????};
????vector<obj> foo()
????{
????????vector<obj> c;
????????c.push_back(obj());
????????cout <<?"---- exit foo ----"?<< endl;
????????return?c;
????}
????int?main()
????{
????????vector<obj> k;
????????k = foo();
????}
首先我們編譯一下這個(gè)函數(shù),運(yùn)行結(jié)果如下:
????tianfang > g++ main.cpp
????tianfang > a.out
????>> create obj?
????>> copy create obj?
????---- exit foo ----
????>> copy create obj?
????tianfang >
可以看到,對obj對象執(zhí)行了兩次構(gòu)造。vector是一個(gè)常用的容器了,我們可以很容易的分析這這兩次拷貝構(gòu)造的時(shí)機(jī):
由于對象的拷貝構(gòu)造的開銷是非常大的,因此我們想就可能避免他們。其中,第一次拷貝構(gòu)造是vector的特性所決定的,不可避免。但第二次拷貝構(gòu)造,在C++ 11中就是可以避免的了。
????tianfang > g++?-std=c++11?main.cpp
????tianfang > a.out
????>> create obj?
????>> copy create obj?
????---- exit foo ----
????tianfang >
可以看到,我們除了加上了一個(gè)-std=c++11選項(xiàng)外,什么都沒干,但現(xiàn)在就把第二次的拷貝構(gòu)造給去掉了。它是如何實(shí)現(xiàn)這一過程的呢?
在老版本中,當(dāng)我們執(zhí)行第二行的賦值操作的時(shí)候,執(zhí)行過程如下:
在C++11的版本中,執(zhí)行過程如下:
關(guān)鍵的過程就是第2步,它不是復(fù)制而是交換,從而避免的成員的拷貝,但效果卻是一樣的。不用修改代碼,性能卻得到了提升,對于程序員來說就是一份免費(fèi)的午餐。
但是,這份免費(fèi)的午餐也不是無條件就可以獲取的,帶上-std=c++11編譯時(shí),如果使用STL代碼可以享用這份午餐,但如果使用我們以前的老代碼發(fā)現(xiàn)還是和以前的功能是一樣的,那么,如何讓我們以前的代碼也能得到這個(gè)效率的提升呢?
? ?
通過交換減少數(shù)據(jù)的拷貝
為了演示如何在我們的代碼中也獲取這個(gè)性能提升,首先我先寫了一個(gè)山寨的vector:
????#include?<iostream>
????#include?<vector>
????using?namespace?std;
????class?obj
????{
????public?:
????????obj() { cout <<?">> create obj "?<< endl; }
????????obj(const?obj& other) { cout <<?">> copy create obj "?<< endl; }
????};
????template?<class?T>
????class?container
????{
????public:
????????T* value;
????public:
????????container() : value(NULL) {};
????????~container() {?delete?value; }?
????????container(const?container& other)
????????{
????????????value =?new?T(*other.value);
????????}
????????const?container&?operator?= (const?container& other)
????????{
????????????delete?value;
????????????value =?new?T(*other.value);
????????????return?*this;
????????}
????????void?push_back(const?T& item)
????????{
????????????delete?value;
????????????value =?new?T(item);
????????}
????};
????container<obj> foo()
????{
????????container<obj> c;
????????c.push_back(obj());
????????cout <<?"---- exit foo ----"?<< endl;
????????return?c;
????}
????int?main()
????{
????????container<obj> k ;
????????k = foo();????
????}
這個(gè)vector只能容納一個(gè)元素,但并不妨礙我們的演示,其功能和前面的例子是一樣的,運(yùn)行這段代碼,結(jié)果如下:
????tianfang > make
????g++?-std=c++11?main.cpp
????tianfang > a.out
????>> create obj?
????>> copy create obj?
????---- exit foo ----
????>> copy create obj?
????tianfang >
如前所述,仍然有兩次拷貝構(gòu)造。其實(shí)前面已經(jīng)說過交換實(shí)現(xiàn)減少拷貝構(gòu)造的原理,那么,我們可以通過修改 '=' 函數(shù)來手動實(shí)現(xiàn)這一過程。
????const?container& operator = (container&?other)
????{
????????T* tmp = value;
????????value =?other.value;
????????other.value = tmp;
????????return?*this;
????}
在VC中運(yùn)行這段代碼,發(fā)現(xiàn)運(yùn)行結(jié)果和預(yù)期一致,
????>> create obj?
????>> copy create obj?
????---- exit foo ----
但是,gcc中卻無法通過編譯,原因很簡單:gcc期望的賦值函數(shù)的參數(shù)是const型的,而這里為了交換成員,而不能使用const型。
那么,雖然gcc中不能生效,是否可以說在vc中就可以以這種形式獲取性能提升呢?答案是否定的。雖然在這段代碼中這么寫沒有問題,但賦值函數(shù)本身是期望復(fù)制功能的,而不是交換。例如,修改后下面的運(yùn)行結(jié)果就不對了。
????int?main()
????{
????????container<obj> k, k2;
????????k = foo();????
????????//預(yù)期結(jié)果是復(fù)制,但執(zhí)行了交換
????????k2 = k;
????}
gcc的告警是有道理的:如果 '=' 函數(shù)實(shí)現(xiàn)的是復(fù)制功能,雖然效率低點(diǎn),但保證了功能正確,但如果實(shí)現(xiàn)的是交換的功能,則不能保證功能一定正確。只有當(dāng) '=' 函數(shù)右邊的對象為一個(gè)臨時(shí)變量的時(shí)候,由于臨時(shí)變量會馬上被刪除掉,此時(shí)的交換和復(fù)制的效果是一樣的。其實(shí)VC也應(yīng)該把這個(gè)告警加上才合適。
PS:對臨時(shí)變量定義和來源不清楚的朋友可以參考一下這篇文章。
現(xiàn)在的問題是:我們無法在賦值函數(shù)里區(qū)分傳入的是一個(gè)臨時(shí)對象還是非臨時(shí)對象,因此只能執(zhí)行復(fù)制操作。為了解決這一問題,c++中引入了一個(gè)新的賦值函數(shù)的重載形式:
????container& operator = (container&&?other)
這個(gè)賦值函數(shù)通常稱為移動賦值函數(shù),和老版本的相比,它有兩點(diǎn)區(qū)別:
現(xiàn)在,我們就有兩個(gè)版本的賦值函數(shù)了,C++11在語法級別也做了適應(yīng):
- 如果入?yún)⑹桥R時(shí)變量,則執(zhí)行移動賦值函數(shù),如果沒有定義移動賦值函數(shù),則執(zhí)行復(fù)制賦值函數(shù)(以保證老版本代碼能編譯通過)
- 如果入?yún)⒉皇桥R時(shí)變量,則執(zhí)行普通的復(fù)制賦值函數(shù)
現(xiàn)在,我們實(shí)現(xiàn)一下山寨版的移動賦值函數(shù):
????container& operator = (container&&?other)
????{
????????delete?value;
????????value =?other.value;
????????other.value =?NULL;
????????return?*this;
????}
運(yùn)行后結(jié)果就和我們期望的那樣,避免了成員的第二次的拷貝構(gòu)造。
和移動賦值函數(shù)相應(yīng)的,也有一個(gè)一個(gè)移動構(gòu)造函數(shù),也最好實(shí)現(xiàn)以下:
????container (container&&?other)
????{
????????value =?other.value;
????????other.value =?NULL;
????}
我們也可以實(shí)現(xiàn)自己的右值引用版的重載函數(shù),這里就不多介紹了。
注意:本文所示的代碼只是為了演示和實(shí)現(xiàn)右值引用,力求簡潔,并沒有寫得很完善(一個(gè)典型的缺失就是在賦值函數(shù)中沒有判斷入?yún)⑹欠袷潜旧?#xff09;,請不要將其應(yīng)用于項(xiàng)目中。
完善的版本請看MSDN文章:如何編寫一個(gè)移動構(gòu)造函數(shù),其相應(yīng)的對右值引用的介紹文章Rvalue引用聲明:&&也非常值得一讀。
?
通過std::move函數(shù)顯式使用交換
首先看一下這段代碼:
????class?bigobj
????{
????public?:
????????bigobj() { cout <<?">> create obj "?<< endl; }
????????bigobj(const?bigobj&?other) { cout <<?">> copy create obj "?<< endl; }
????????bigobj(bigobj&&?other) { cout <<?">> move create obj "?<< endl; }
????};
????int?main()
????{
????????list<bigobj> list;
????????for(int?i = 0; i < 3; i++)
????????{
????????????bigobj?obj;
????????????list.push_back(obj);
????????}
????}
運(yùn)行的時(shí)候就會發(fā)現(xiàn):雖然我們定義了移動構(gòu)造函數(shù),但是它仍然會執(zhí)行拷貝構(gòu)造函數(shù)。這是因?yàn)榫幾g器并不認(rèn)為obj是臨時(shí)變量。關(guān)于什么變量才是臨時(shí)變量,前文已經(jīng)給了個(gè)鏈接來說明它,簡單的說,我們能夠看到的命名變量都不是臨時(shí)變量。
雖然obj對象不是語言級別的臨時(shí)變量,但是從功能上來看,它就是一個(gè)臨時(shí)變量,是可以使用移動構(gòu)造函數(shù)來消除拷貝帶來的性能損失的。為了解決這一問題,C++提供了一個(gè)move函數(shù)來把obj變量強(qiáng)制轉(zhuǎn)換為右值引用,這樣就可以使用移動構(gòu)造函數(shù)了。
????for(int?i = 0; i < 3; i++)
????{
????????bigobj?obj;
????????list.push_back(std::move(obj));
????}
不過,需要注意的是,和系統(tǒng)識別的臨時(shí)變量而自動使用右值引用不同,這種強(qiáng)制轉(zhuǎn)換是有一定的風(fēng)險(xiǎn)的,由于在push_back后執(zhí)行了交換操作,如果再次使用它會出現(xiàn)非預(yù)期的結(jié)果,只有能確定該變量不會再次被使用才能執(zhí)行這種轉(zhuǎn)換。
總結(jié)
以上是生活随笔為你收集整理的C++ 11 中的右值引用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于回调函数
- 下一篇: windows配置gvim高效率编程(c