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?- #include <iostream>
- usingnamespace std;
- class A{
- private:
- int data;//data
- int *pi;//point to data
- public:
- //禁止隱式轉換
- A(){
- }
- explicit A(int i):data(i){
- cout<<"normal constuctor1!"<<endl;
- pi=&data;
- }
- A(const A &a){
- data=a.data;
- cout<<"copy constructor!"<<endl;
- pi=&data;
- }
- A(A &&a){
- cout<<"move constructor!"<<endl;
- //直接移動a.pi到pi
- pi=a.pi;
- //修改源pi
- a.pi=nullptr;
- a.data=0;
- }
- //A(A &&a)=delete;
- A operator+(const A &a){
- A temp(data+a.data);
- cout<<endl<<"operator+ called!show temp!"<<endl;
- temp.show();
- cout<<endl;
- return temp;
- }
- void show()const{
- cout<<"pi="<<pi<<" data="<<data<<endl;
- }
- };
- int main(){
- int i=99;
- A a(10);
- a.show();
- A b(i);
- b.show();
- A c(b);
- c.show();
- A d(b+c);
- cout<<"show d!"<<endl;
- d.show();
- }
看出來什麼問題沒有?
對,好像並沒有呼叫移動建構函式!
但是有沒有發現!temp和d的pi都是指向同一個地方那個?這是什麼情況?
原來是因為GCC自帶的右值語義!
也就是,編譯器GCC會幫你自動優化!
不信請看下面例子!我們利用C++11的delete特性!
[cpp] view plaincopyprint?- #include <iostream>
- usingnamespace std;
- class A{
- private:
- int data;//data
- int *pi;//point to data
- public:
- //禁止隱式轉換
- A(){
- }
- explicit A(int i):data(i){
- cout<<"normal constuctor1!"<<endl;
- pi=&data;
- }
- A(const A &a){
- data=a.data;
- cout<<"copy constructor!"<<endl;
- pi=&data;
- }
- /*
- A(A &&a){
- cout<<"move constructor!"<<endl;
- //直接移動a.pi到pi
- pi=a.pi;
- //修改源pi
- a.pi=nullptr;
- a.data=0;
- }*/
- A(A &&a)=delete;
- A operator+(const A &a){
- A temp(data+a.data);
- cout<<endl<<"operator+ called!show temp!"<<endl;
- temp.show();
- cout<<endl;
- return temp;
- }
- void show()const{
- cout<<"pi="<<pi<<" data="<<data<<endl;
- }
- };
- int main(){
- int i=99;
- A a(10);
- a.show();
- A b(i);
- b.show();
- A c(b);
- c.show();
- A d(b+c);
- cout<<"show d!"<<endl;
- d.show();
- }
執行結果:
也就是說,在return temp;這一句上將要呼叫A(A&&)這個建構函式;
但是現在這個函式被我們顯式刪除了!
b+c也是一個右值!也是需要呼叫移動建構函式的!
因此上一個例子實際上是呼叫了移動語義的建構函式!