国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > C++ > 正文

C++11移動語義探討——從臨時對象到右值引用

2019-11-11 01:55:20
字體:
來源:轉載
供稿:網友

一.前言

這篇文章主要談談c++11中引入的右值引用概念和移動語義概念。以及這些東西可能在我們編程中帶來哪些體驗、便捷或者是代碼效率的提高。 文章主要分為以下三點:

臨時對象的產生何謂右值引用何謂移動語義

二.臨時對象的產生

在我們以往的編程過程中可能很少會注意到臨時對象(變量)的問題,因為這已經不太是程序層面上的問題,而更多是編譯器上的事情。編譯器在編譯代碼過程中為了實現某些代碼可能會產生出一些臨時對象(變量)來滿足一些效果,比如:

void Test(MyClass obj) {}MyClass myclass;Test(myclass);//可能編譯器會做如下改寫:void Test(MyClass& obj){}MyClass myclass;MyClass obj(myclass); //產生的臨時對象Test(obj);obj.~MyClass();

又或者是這樣:

double db = 5.5;int it = st; //產生int臨時變量賦值給it

當然,這些臨時對象并不需要程序員參與干涉,甚至臨時對象這些行為對于程序員而言是透明的。但是,我們仍然需要對其進行一定的了解,這對后面c++11引入的右值引用和移動語義的理解會帶來莫大的幫助。

產生臨時變量的地方有很多,不同編譯器在某些細節上可能處理方式也略有不同。不過具體說來有以下幾種:

不同類型(對象)變量的轉換函數以pass by value傳遞參數的時候表達式求值中

這里文章就不繼續深入探討了,下面我們來談臨時對象對編程的一些影響和右值引用的引入。

三.何謂右值引用

假如我們設計一個復數類,可能大概如下:

class Complex{public: Complex() :_real(0), _imaginary(0) {} Complex(double real, double imaginary) :_real(real), _imaginary(imaginary) {} Complex(Complex& cp) :_real(cp._real), _imaginary(cp._imaginary) {}PRivate: double _real; //實部 double _imaginary; //虛部};

嗯,先不看其他函數,就此而言大概是”沒有太多問題的”。 但是如果在如下使用時候:

//求共軛復數//為Complex的友元Complex getConjugate(Complex cp){ cp._imaginary = -cp._imaginary; return cp;}int main(){ Complex a(1.0, -5.0); Complex b = getConjugate(a); // Error : no match return 0;}

有的編譯器下會報錯。MSVC 沒有, GUN GCC 報錯了。原因呢很簡單,編譯器提示找不到匹配的拷貝構造函數。 我們在前面的介紹知道,這里函數返回的其實是一個臨時對象,而我們拷貝構造函數中的參數類型為Complex&。而這里,試圖將一個引用綁定到臨時變量上,這顯然是不允許的。因為,規定不允許改變臨時變量。 所以我們也知道了解決方案:將拷貝構造函數修改為 Complex(const Complex& cp)

以上問題就告一段落了,我們來到一個新問題,我們試圖寫一個mystring類:

class MyString{ friend ostream& Operator << (ostream& os, MyString mystr);public: MyString() :_ptr(0) {} MyString(const char* ptr) { int len = strlen(ptr); _ptr = new char[len + 1]; strcpy(_ptr, ptr); } MyString(const MyString& mystr) { int len = strlen(mystr._ptr); _ptr = new char[len + 1]; strcpy(_ptr, mystr._ptr); } //MyString& operator = (const MyString&){ ... } ~MyString() { delete _ptr; }private: char* _ptr;};ostream& operator << (ostream& os, MyString mystr){ return os << mystr._ptr;}int main(){ MyString a = "123"; cout << a << endl; return 0;}

由于篇幅,我就只是給出了必要的函數,operator=()就沒有給出了。所以賦值只是淺拷貝,不過這里并不關注這個。

我們注意看拷貝構造函數,由于前篇提到的原因,這里的拷貝函數我們加上了const,使得拷貝函數能夠接受臨時變量。 當傳入參數是臨時變量的情況時,我們仔細思考一下: 我們知道臨時變量很快(大概就是函數返回之后)就會銷毀,然而在這樣的情況下,行為仿佛是:將一份字符串拷貝一份,然后就將原稿銷毀了。對,為什么不能將臨時對象的字符串”拿為己用”呢?這樣我們就免去了內存的開辟和繁瑣的字符串賦值了。嗯,大概函數如下:

MyString(....):_ptr(mystr._ptr){ mystr._ptr = NULL;}

對,這樣我們就將_ptr指向的真正的字符串的所有權拿過來了。但是,這里我把參數空了出來,是的,我們要去設法判斷什么時候是臨時對象。然而這在c++1.0是不太可行的。

所以,在C++11中引入了一個新的概念——右值引用(&&),右值引用專門用于引用右值(臨時對象、匿名對象)。

那么有了語言的支持,對于上面的情況,我們可以進行如下的改寫:

class MyString{ friend ostream& operator << (ostream& os, MyString mystr);public: //上面不變... MyString(const MyString& mystr) { int len = strlen(mystr._ptr); _ptr = new char[len + 1]; strcpy(_ptr, mystr._ptr); } MyString(MyString&& mystr) :_ptr(mystr._ptr){ mystr._ptr = NULL; } //下面不變...};

這樣的話,我們當傳入臨時變量的時候,哦,不對,我們應該改口叫做右值。 那么調用的編譯器就是MyString(MyString&& mystr)版本,在函數中,我們剝奪了右值的字符串,并且將mystr._ptr = NULL, 這很重要,因為防止臨時變量析構的時候銷毀字符串。

四.何謂移動語義

其實上一段在介紹mystring設計的時候已經使用了移動語義,就是程序員提供移動構造函數(參數為右值引用的構造函數)使得當用臨時變量構造新對象的時候可以提供較好的優化。 下面給就用上面的mystring作為例子來測一下提供了移動構造函數和沒有提供移動構造函數的時候,分別用臨時變量去構造對象的對比:

  100000長度字符串,10000次初始化
提供移動構造 22ms
不提供移動構造 376ms

可以看出來提供移動構造函數在某些情況下可以將性能極大地提高。特別對于某些需要深拷貝的對象來說。特別的,當這一類的對象配合標準庫的容器的時候(vector,deque),如果提供移動構造函數,將會在容器內存重分配的時候帶來極大效率的優化。

當然,除了編譯器創建的臨時對象來作為右值以外,我們也可以使用std::move來得到一個左值的右值引用。 我們先來看move()的源碼

template<class _Ty> inlineconstexpr typename remove_reference<_Ty>::type&&move(_Ty&& _Arg) _NOEXCEPT{ return (static_cast<typename remove_reference<_Ty>::type&&>(_Arg));}

當傳入左值Type&的時候 _Ty被識別為Type& , remove_reference<_Ty>::type 為 Type,返回的就是 Type&& 當傳入右值Type&&的時候_Ty被識別為Type&&, remove_reference<_Ty>::type 為 Type,返回的就是 Type&&

也就是無論傳入一個什么值,都將返回這個值的右值引用,這樣就可以顯式讓編譯器調用類中的移動構造函數了。不過值得注意的是,程序員必須恪守——被當作右值的對象在移動構造之后在重新賦值之前絕不使用。 如下:

vector<int> a = {1,2,3,4,5};vector<int> b(move(a)); //這里調用的是b的移動構造函數//a在重新賦值之前絕不使用。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

主站蜘蛛池模板: 汽车| 岚皋县| 南川市| 苏尼特左旗| 吴江市| 类乌齐县| 天等县| 扶绥县| 棋牌| 宁津县| 澄江县| 吉水县| 伊宁市| 项城市| 晋江市| 高邮市| 岳西县| 孝义市| 高平市| 明星| 阳谷县| 昭苏县| 含山县| 赤水市| 页游| 健康| 广昌县| 宜宾县| 比如县| 茶陵县| 华阴市| 西乌| 邢台县| 锦州市| 怀仁县| 六盘水市| 清新县| 将乐县| 罗山县| 宁南县| 承德县|