1. 程式人生 > >C++11 新特性 移動語義

C++11 新特性 移動語義

C++11支援移動語義。

一:為什麼需要移動語義和什麼是移動語義

我們先來看看C++11之前的複製過程。假設有下列程式碼:

vector<string> v1(1000000);//v1存放著100W個string,假設每個string長度為1000

vector<string> v2(v1);//使用v1初始化v2

vector和string類都使用動態記憶體分配,因此他們必須定義使用他們自己的new版本的複製建構函式。

複製建構函式vector<string>將使用new給1000W個string分配物件,而每個string 將使用new給每個string分配1000個字元的空間大小。接下來全部的都將從v1中逐個複製string到v2中,這裡的工作量非常大,但是並沒有問題。

但真的沒有問題嗎?有時候答案是否定的。例如,假設有一個函式,他返回一個vector<string>物件。

vector<string>copyVector(const vector<string> &v){

vector<string> temp;

//複製100W個string到temp

return temp;

}

接下來,以以下方式呼叫這個函式。

vector<string> v1(1000000);//v1存放著100W個string,假設每個string長度為1000

vector<string> v2=copyVector(v1);//使用v1初始化v2

構造v2的時候,編譯器先利用v1構造生成了一個temp副本,然後將temp複製給一個臨時物件,返回給v2,v2利用該臨時物件,構造自己。

這將導致非常巨大的工作量!做了大量的無用功(將temp複製給一個臨時物件,返回給v2,v2利用該臨時物件,構造自己)。在這之後,temp這個臨時的物件被刪除了,返回的那個temp副本臨時物件也被刪除了,如果編譯器能夠將temp的所有權直接轉移給v2不是更好嗎?也就是說,不用將100W個string多次複製到新的地方,再刪除原來的字元,而是直接保留字元,並將v2與之關聯。這類似於計算機中檔案的移動。實際檔案還保留在原來的地方,而只是記錄修改了,這種方法稱之為移動語義

移動語義避免了移動原始資料,而只是修改了記錄。

要實現移動語義,必須讓編譯器知道什麼時候需要複製,什麼時候不需要複製。這就是右值引用發揮最大作用的地方

二:如何實現移動語義

看一個簡單的使用移動語義的例子。

[cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. #include <iostream>
  2. usingnamespace std;  
  3. class A{  
  4. private:  
  5.     int data;//data
  6.     int *pi;//point to data
  7. public:  
  8.     //禁止隱式轉換
  9.     A(){  
  10.     }  
  11.     explicit A(int i):data(i){  
  12.         cout<<"normal constuctor1!"<<endl;    
  13.         pi=&data;  
  14.     }  
[cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1.     A(const A &a){  
  2.         data=a.data;  
  3.         cout<<"copy constructor!"<<endl;  
  4.         pi=&data;  
  5.     }  
  6.     A(A &&a){  
  7.         cout<<"move constructor!"<<endl;  
  8.         //直接移動a.pi到pi
  9.         pi=a.pi;  
  10.         //修改源pi
  11.         a.pi=nullptr;  
  12.         a.data=0;  
  13.     }  
  14.     //A(A &&a)=delete;
  15.     A operator+(const A &a){  
  16.         A temp(data+a.data);  
  17.         cout<<endl<<"operator+ called!show temp!"<<endl;  
  18.         temp.show();  
  19.         cout<<endl;  
  20.         return temp;  
  21.     }  
  22.     void show()const{  
  23.         cout<<"pi="<<pi<<"   data="<<data<<endl;  
  24.     }  
  25. };  
  26. int main(){  
  27.     int i=99;  
  28.     A a(10);  
  29.     a.show();  
  30.     A b(i);  
  31.     b.show();  
  32.     A c(b);  
  33.     c.show();  
  34.     A d(b+c);  
  35.     cout<<"show d!"<<endl;  
  36.     d.show();  
  37. }  


看出來什麼問題沒有?

對,好像並沒有呼叫移動建構函式!

但是有沒有發現!temp和d的pi都是指向同一個地方那個?這是什麼情況?

原來是因為GCC自帶的右值語義!

也就是,編譯器GCC會幫你自動優化!

不信請看下面例子!我們利用C++11的delete特性!

[cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. #include <iostream>
  2. usingnamespace std;  
  3. class A{  
  4. private:  
  5.     int data;//data
  6.     int *pi;//point to data
  7. public:  
  8.     //禁止隱式轉換
  9.     A(){  
  10.     }  
  11.     explicit A(int i):data(i){  
  12.         cout<<"normal constuctor1!"<<endl;    
  13.         pi=&data;  
  14.     }  
  15.     A(const A &a){  
  16.         data=a.data;  
  17.         cout<<"copy constructor!"<<endl;  
  18.         pi=&data;  
  19.     }  
  20.     /* 
  21.     A(A &&a){ 
  22.         cout<<"move constructor!"<<endl; 
  23.         //直接移動a.pi到pi 
  24.         pi=a.pi; 
  25.         //修改源pi 
  26.         a.pi=nullptr; 
  27.         a.data=0; 
  28.     }*/
  29.     A(A &&a)=delete;  
  30.     A operator+(const A &a){  
  31.         A temp(data+a.data);  
  32.         cout<<endl<<"operator+ called!show temp!"<<endl;  
  33.         temp.show();  
  34.         cout<<endl;  
  35.         return temp;  
  36.     }  
  37.     void show()const{  
  38.         cout<<"pi="<<pi<<"   data="<<data<<endl;  
  39.     }  
  40. };  
  41. int main(){  
  42.     int i=99;  
  43.     A a(10);  
  44.     a.show();  
  45.     A b(i);  
  46.     b.show();  
  47.     A c(b);  
  48.     c.show();  
  49.     A d(b+c);  
  50.     cout<<"show d!"<<endl;  
  51.     d.show();  
  52. }  

執行結果:


也就是說,在return temp;這一句上將要呼叫A(A&&)這個建構函式;

但是現在這個函式被我們顯式刪除了!

b+c也是一個右值!也是需要呼叫移動建構函式的!

因此上一個例子實際上是呼叫了移動語義的建構函式!