標籤:另一個 amp .com ges 需要 最好 結合 cout 第一個
C++移動建構函式以及move語句簡單介紹首先看一個小例子:
#include <iostream>#include <cstring>#include <cstdlib>#include <vector>using namespace std;int main(){ string st = "I love xing"; vector<string> vc ; vc.push_back(move(st)); cout<<vc[0]<<endl; if(!st.empty()) cout<<st<<endl; return 0;}
結果為:
#include <iostream>#include <cstring>#include <cstdlib>#include <vector>using namespace std;int main(){ string st = "I love xing"; vector<string> vc ; vc.push_back(st); cout<<vc[0]<<endl; if(!st.empty()) cout<<st<<endl; return 0;}
結果為:
這兩個小程式唯一的不同是調用vc.push_back()將字串插入到容器中去時,第一段代碼使用了move語句,而第二段代碼沒有使用move語句。輸出的結果差異也很明顯,第一段代碼中,原來的字串st已經為空白,而第二段代碼中,原來的字串st的內容沒有變化。
好,記住這兩端代碼的輸出結果之間的差異。下面我們簡單介紹一下移動建構函式。
在介紹移動建構函式之前,我們先要回顧一下拷貝建構函式。
我們都知道,C++在三種情況下會調用拷貝建構函式(可能有紕漏),第一種情況是函數形實結合時,第二種情況是函數返回時,函數棧區的對象會複製一份到函數的返回去,第三種情況是用一個對象初始化另一個對象時也會調用拷貝建構函式。
除了這三種情況下會調用拷貝建構函式,另外如果將一個對象賦值給另一個對象,這個時候回調用重載的賦值運算子函數。
無論是拷貝建構函式,還是重載的賦值運算子函數,我記得當時在上C++課的時候,老師再三強調,一定要注意指標的淺層複製問題。
這裡在簡單回憶一下拷貝建構函式中的淺層複製問題
首先看一個淺層複製的代碼
#include <iostream>#include <cstring>#include <cstdlib>#include <vector>using namespace std;class Str{ public: char *value; Str(char s[]) { cout<<"調用建構函式..."<<endl; int len = strlen(s); value = new char[len + 1]; memset(value,0,len + 1); strcpy(value,s); } Str(Str &v) { cout<<"調用拷貝建構函式..."<<endl; this->value = v.value; } ~Str() { cout<<"調用解構函式..."<<endl; if(value != NULL) delete[] value; }};int main(){ char s[] = "I love BIT"; Str *a = new Str(s); Str *b = new Str(*a); delete a; cout<<"b對象中的字串為:"<<b->value<<endl; delete b; return 0;}
輸出結果為:
首先結果並不符合預期,我們希望b對象中的字串也是I love BIT但是輸出為空白,這是因為b->value和a->value指向了同一片記憶體地區,當delete a的時候,該記憶體地區已經被收回,所以再用b->value訪問那塊記憶體實際上是不合適的,而且,雖然我運行時程式沒有崩潰,但是程式存在崩潰的風險呀,因為當delete b的時候,那塊記憶體地區又被釋放了一次,兩次釋放同一塊記憶體,相當危險呀。
我們用valgrind檢查一下,發現,相當多的記憶體錯誤呀!
其中就有一個Invalid free 也就是刪除b的時候調用解構函式,對已經釋放掉對空間又釋放了一次。
那麼深層複製應該怎樣寫呢?
代碼如下:
#include <iostream>#include <cstring>#include <cstdlib>#include <vector>using namespace std;class Str{ public: char *value; Str(char s[]) { cout<<"調用建構函式..."<<endl; int len = strlen(s); value = new char[len + 1]; memset(value,0,len + 1); strcpy(value,s); } Str(Str &v) { cout<<"調用拷貝建構函式..."<<endl; int len = strlen(v.value); value = new char[len + 1]; memset(value,0,len + 1); strcpy(value,v.value); } ~Str() { cout<<"調用解構函式..."<<endl; if(value != NULL) { delete[] value; value = NULL; } }};int main(){ char s[] = "I love BIT"; Str *a = new Str(s); Str *b = new Str(*a); delete a; cout<<"b對象中的字串為:"<<b->value<<endl; delete b; return 0;}
結果為:
這次達到了我們預想的效果,而且,用valgrind檢測一下,發現,沒有記憶體錯誤!
所以,寫拷貝建構函式的時候,切記要注意指標的淺層複製問題呀!
好的,回顧了一下拷貝建構函式,下面回到移動建構函式上來。
有時候我們會遇到這樣一種情況,我們用對象a初始化對象b,後對象a我們就不在使用了,但是對象a的空間還在呀(在析構之前),既然拷貝建構函式,實際上就是把a對象的內容複寫一份到b中,那麼為什麼我們不能直接使用a的空間呢?這樣就避免了新的空間的分配,大大降低了構造的成本。這就是移動建構函式設計的初衷。
下面這個圖,很好地說明了拷貝建構函式和移動建構函式的區別。
看明白了嗎?
通俗一點的解釋就是,拷貝建構函式中,對於指標,我們一定要採用深層複製,而移動建構函式中,對於指標,我們採用淺層複製。
但是上面提到,指標的淺層複製是非常危險的呀。沒錯,確實很危險,而且通過上面的例子,我們也可以看出,淺層複製之所以危險,是因為兩個指標共同指向一片記憶體空間,若第一個指標將其釋放,另一個指標的指向就不合法了。所以我們只要避免第一個指標釋放空間就可以了。避免的方法就是將第一個指標(比如a->value)置為NULL,這樣在調用解構函式的時候,由於有判斷是否為NULL的語句,所以析構a的時候並不會回收a->value指向的空間(同時也是b->value指向的空間)
所以我們可以把上面的拷貝建構函式的代碼修改一下:
#include <iostream>#include <cstring>#include <cstdlib>#include <vector>using namespace std;class Str{ public: char *value; Str(char s[]) { cout<<"調用建構函式..."<<endl; int len = strlen(s); value = new char[len + 1]; memset(value,0,len + 1); strcpy(value,s); } Str(Str &v) { cout<<"調用拷貝建構函式..."<<endl; this->value = v.value; v.value = NULL; } ~Str() { cout<<"調用解構函式..."<<endl; if(value != NULL) delete[] value; }};int main(){ char s[] = "I love BIT"; Str *a = new Str(s); Str *b = new Str(*a); delete a; cout<<"b對象中的字串為:"<<b->value<<endl; delete b; return 0;}
結果為:
修改後的拷貝建構函式,採用了淺層複製,但是結果仍能夠達到我們想要的效果,關鍵在於在拷貝建構函式中,最後我們將v.value置為了NULL,這樣在析構a的時候,就不會回收a->value指向的記憶體空間。
這樣用a初始化b的過程中,實際上我們就減少了開闢記憶體,構造成本就降低了。
但要注意,我們這樣使用有一個前提是:用a初始化b後,a我們就不需要了,最好是初始化完成後就將a析構。如果說,我們用a初始化了b後,仍要對a進行操作,用這種淺層複製的方法就不合適了。
所以C++引入了移動建構函式,專門處理這種,用a初始化b後,就將a析構的情況。
*************************************************************
**移動建構函式的參數和拷貝建構函式不同,拷貝建構函式的參數是一個左值引用,但是移動建構函式的初值是一個右值引用。(關於右值引用大家可以看我之前的文章,或者尋找其他資料)。這意味著,移動建構函式的參數是一個右值或者將亡值的引用。也就是說,只用用一個右值,或者將亡值初始化另一個對象的時候,才會調用移動建構函式。而那個move語句,就是將一個左值變成一個將亡值。
移動建構函式應用最多的地方就是STL中
給出一個代碼,大家自行驗證使用move和不適用move的區別吧
#include <iostream>#include <cstring>#include <cstdlib>#include <vector>using namespace std;class Str{ public: char *str; Str(char value[]) { cout<<"普通建構函式..."<<endl; str = NULL; int len = strlen(value); str = (char *)malloc(len + 1); memset(str,0,len + 1); strcpy(str,value); } Str(const Str &s) { cout<<"拷貝建構函式..."<<endl; str = NULL; int len = strlen(s.str); str = (char *)malloc(len + 1); memset(str,0,len + 1); strcpy(str,s.str); } Str(Str &&s) { cout<<"移動建構函式..."<<endl; str = NULL; str = s.str; s.str = NULL; } ~Str() { cout<<"解構函式"<<endl; if(str != NULL) { free(str); str = NULL; } }};int main(){ char value[] = "I love zx"; Str s(value); vector<Str> vs; //vs.push_back(move(s)); vs.push_back(s); cout<<vs[0].str<<endl; if(s.str != NULL) cout<<s.str<<endl; return 0;}
如果你覺得對你有用,請咱一個吧~~~
C++移動建構函式以及move語句簡單介紹