C++中的移動構(gòu)造函數(shù)及move語句示例詳解
前言
本文主要給大家介紹了關(guān)于C++中移動構(gòu)造函數(shù)及move語句的相關(guān)內(nèi)容,分享出來供大家參考學(xué)習(xí),下面話不多說了,來一起看看詳細(xì)的介紹吧。
首先看一個小例子:
#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; }
結(jié)果為:
#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; }
結(jié)果為:
這兩個小程序唯一的不同是調(diào)用vc.push_back()將字符串插入到容器中去時(shí),第一段代碼使用了move語句,而第二段代碼沒有使用move語句。輸出的結(jié)果差異也很明顯,第一段代碼中,原來的字符串st已經(jīng)為空,而第二段代碼中,原來的字符串st的內(nèi)容沒有變化。
好,記住這兩端代碼的輸出結(jié)果之間的差異。下面我們簡單介紹一下移動構(gòu)造函數(shù)。
在介紹移動構(gòu)造函數(shù)之前,我們先要回顧一下拷貝構(gòu)造函數(shù)。
我們都知道,C++在三種情況下會調(diào)用拷貝構(gòu)造函數(shù)(可能有紕漏),第一種情況是函數(shù)形實(shí)結(jié)合時(shí),第二種情況是函數(shù)返回時(shí),函數(shù)棧區(qū)的對象會復(fù)制一份到函數(shù)的返回去,第三種情況是用一個對象初始化另一個對象時(shí)也會調(diào)用拷貝構(gòu)造函數(shù)。
除了這三種情況下會調(diào)用拷貝構(gòu)造函數(shù),另外如果將一個對象賦值給另一個對象,這個時(shí)候回調(diào)用重載的賦值運(yùn)算符函數(shù)。
無論是拷貝構(gòu)造函數(shù),還是重載的賦值運(yùn)算符函數(shù),我記得當(dāng)時(shí)在上C++課的時(shí)候,老師再三強(qiáng)調(diào),一定要注意指針的淺層復(fù)制問題。
這里在簡單回憶一下拷貝構(gòu)造函數(shù)中的淺層復(fù)制問題
首先看一個淺層復(fù)制的代碼
#include <iostream> #include <cstring> #include <cstdlib> #include <vector> using namespace std; class Str{ public: char *value; Str(char s[]) { cout<<"調(diào)用構(gòu)造函數(shù)..."<<endl; int len = strlen(s); value = new char[len + 1]; memset(value,0,len + 1); strcpy(value,s); } Str(Str &v) { cout<<"調(diào)用拷貝構(gòu)造函數(shù)..."<<endl; this->value = v.value; } ~Str() { cout<<"調(diào)用析構(gòu)函數(shù)..."<<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; }
輸出結(jié)果為:
首先結(jié)果并不符合預(yù)期,我們希望b對象中的字符串也是I love BIT但是輸出為空,這是因?yàn)閎->value和a->value指向了同一片內(nèi)存區(qū)域,當(dāng)delete a的時(shí)候,該內(nèi)存區(qū)域已經(jīng)被收回,所以再用b->value訪問那塊內(nèi)存實(shí)際上是不合適的,而且,雖然我運(yùn)行時(shí)程序沒有崩潰,但是程序存在崩潰的風(fēng)險(xiǎn)呀,因?yàn)楫?dāng)delete b的時(shí)候,那塊內(nèi)存區(qū)域又被釋放了一次,兩次釋放同一塊內(nèi)存,相當(dāng)危險(xiǎn)呀。
我們用valgrind檢查一下,發(fā)現(xiàn),相當(dāng)多的內(nèi)存錯誤呀!
其中就有一個Invalid free 也就是刪除b的時(shí)候調(diào)用析構(gòu)函數(shù),對已經(jīng)釋放掉對空間又釋放了一次。
那么深層復(fù)制應(yīng)該怎樣寫呢?
代碼如下:
#include <iostream> #include <cstring> #include <cstdlib> #include <vector> using namespace std; class Str{ public: char *value; Str(char s[]) { cout<<"調(diào)用構(gòu)造函數(shù)..."<<endl; int len = strlen(s); value = new char[len + 1]; memset(value,0,len + 1); strcpy(value,s); } Str(Str &v) { cout<<"調(diào)用拷貝構(gòu)造函數(shù)..."<<endl; int len = strlen(v.value); value = new char[len + 1]; memset(value,0,len + 1); strcpy(value,v.value); } ~Str() { cout<<"調(diào)用析構(gòu)函數(shù)..."<<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; }
結(jié)果為:
這次達(dá)到了我們預(yù)想的效果,而且,用valgrind檢測一下,發(fā)現(xiàn),沒有內(nèi)存錯誤!
所以,寫拷貝構(gòu)造函數(shù)的時(shí)候,切記要注意指針的淺層復(fù)制問題呀!
好的,回顧了一下拷貝構(gòu)造函數(shù),下面回到移動構(gòu)造函數(shù)上來。
有時(shí)候我們會遇到這樣一種情況,我們用對象a初始化對象b,后對象a我們就不在使用了,但是對象a的空間還在呀(在析構(gòu)之前),既然拷貝構(gòu)造函數(shù),實(shí)際上就是把a(bǔ)對象的內(nèi)容復(fù)制一份到b中,那么為什么我們不能直接使用a的空間呢?這樣就避免了新的空間的分配,大大降低了構(gòu)造的成本。這就是移動構(gòu)造函數(shù)設(shè)計(jì)的初衷。
下面這個圖,很好地說明了拷貝構(gòu)造函數(shù)和移動構(gòu)造函數(shù)的區(qū)別。
看明白了嗎?
通俗一點(diǎn)的解釋就是,拷貝構(gòu)造函數(shù)中,對于指針,我們一定要采用深層復(fù)制,而移動構(gòu)造函數(shù)中,對于指針,我們采用淺層復(fù)制。
但是上面提到,指針的淺層復(fù)制是非常危險(xiǎn)的呀。沒錯,確實(shí)很危險(xiǎn),而且通過上面的例子,我們也可以看出,淺層復(fù)制之所以危險(xiǎn),是因?yàn)閮蓚€指針共同指向一片內(nèi)存空間,若第一個指針將其釋放,另一個指針的指向就不合法了。所以我們只要避免第一個指針釋放空間就可以了。避免的方法就是將第一個指針(比如a->value)置為NULL,這樣在調(diào)用析構(gòu)函數(shù)的時(shí)候,由于有判斷是否為NULL的語句,所以析構(gòu)a的時(shí)候并不會回收a->value指向的空間(同時(shí)也是b->value指向的空間)
所以我們可以把上面的拷貝構(gòu)造函數(shù)的代碼修改一下:
#include <iostream> #include <cstring> #include <cstdlib> #include <vector> using namespace std; class Str{ public: char *value; Str(char s[]) { cout<<"調(diào)用構(gòu)造函數(shù)..."<<endl; int len = strlen(s); value = new char[len + 1]; memset(value,0,len + 1); strcpy(value,s); } Str(Str &v) { cout<<"調(diào)用拷貝構(gòu)造函數(shù)..."<<endl; this->value = v.value; v.value = NULL; } ~Str() { cout<<"調(diào)用析構(gòu)函數(shù)..."<<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; }
結(jié)果為:
修改后的拷貝構(gòu)造函數(shù),采用了淺層復(fù)制,但是結(jié)果仍能夠達(dá)到我們想要的效果,關(guān)鍵在于在拷貝構(gòu)造函數(shù)中,最后我們將v.value置為了NULL,這樣在析構(gòu)a的時(shí)候,就不會回收a->value指向的內(nèi)存空間。
這樣用a初始化b的過程中,實(shí)際上我們就減少了開辟內(nèi)存,構(gòu)造成本就降低了。
但要注意,我們這樣使用有一個前提是:用a初始化b后,a我們就不需要了,最好是初始化完成后就將a析構(gòu)。如果說,我們用a初始化了b后,仍要對a進(jìn)行操作,用這種淺層復(fù)制的方法就不合適了。
所以C++引入了移動構(gòu)造函數(shù),專門處理這種,用a初始化b后,就將a析構(gòu)的情況。
*************************************************************
**移動構(gòu)造函數(shù)的參數(shù)和拷貝構(gòu)造函數(shù)不同,拷貝構(gòu)造函數(shù)的參數(shù)是一個左值引用,但是移動構(gòu)造函數(shù)的初值是一個右值引用。(關(guān)于右值引用大家可以看我之前的文章,或者查找其他資料)。這意味著,移動構(gòu)造函數(shù)的參數(shù)是一個右值或者將亡值的引用。也就是說,只用用一個右值,或者將亡值初始化另一個對象的時(shí)候,才會調(diào)用移動構(gòu)造函數(shù)。而那個move語句,就是將一個左值變成一個將亡值。
移動構(gòu)造函數(shù)應(yīng)用最多的地方就是STL中
給出一個代碼,大家自行驗(yàn)證使用move和不適用move的區(qū)別吧
#include <iostream> #include <cstring> #include <cstdlib> #include <vector> using namespace std; class Str{ public: char *str; Str(char value[]) { cout<<"普通構(gòu)造函數(shù)..."<<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<<"拷貝構(gòu)造函數(shù)..."<<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<<"移動構(gòu)造函數(shù)..."<<endl; str = NULL; str = s.str; s.str = NULL; } ~Str() { cout<<"析構(gòu)函數(shù)"<<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; }
總結(jié)
以上就是這篇文章的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
- 詳解C++中對構(gòu)造函數(shù)和賦值運(yùn)算符的復(fù)制和移動操作
- C++11中std::move、std::forward、左右值引用、移動構(gòu)造函數(shù)的測試問題
- C++中SetConsoleCursorPosition()移動光標(biāo)函數(shù)的用法大全
- C++11 移動構(gòu)造函數(shù)的使用
- C++超詳細(xì)講解拷貝構(gòu)造函數(shù)
- C++超詳細(xì)講解構(gòu)造函數(shù)
- 聊聊C++中右值引用和移動構(gòu)造函數(shù)的使用
- C++賦值函數(shù)+移動賦值函數(shù)+移動構(gòu)造函數(shù)詳解
相關(guān)文章
數(shù)據(jù)結(jié)構(gòu)之?dāng)?shù)組Array實(shí)例詳解
這篇文章主要介紹了數(shù)據(jù)結(jié)構(gòu)之?dāng)?shù)組Array實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05C語言實(shí)現(xiàn)奇數(shù)階魔方陣的方法
這篇文章主要介紹了C語言實(shí)現(xiàn)奇數(shù)階魔方陣的方法,涉及數(shù)組及相關(guān)數(shù)學(xué)函數(shù)的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02C++基于hook iat改變Messagebox實(shí)例
這篇文章主要介紹了C++基于hook iat改變Messagebox的方法,以實(shí)例形式展示了針對IAT(即導(dǎo)入地址表)以及hook的操作,有助于深入理解Windows程序設(shè)計(jì)原理,需要的朋友可以參考下2014-10-10Matlab實(shí)現(xiàn)繪制立體玫瑰花的示例代碼
這篇文章主要介紹了如何利用Matlab實(shí)現(xiàn)繪制更立體的玫瑰花,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Matlab有一定的幫助,需要的可以參考一下2023-02-02C++構(gòu)造函數(shù)初始化列表的實(shí)現(xiàn)詳解
構(gòu)造函數(shù)主要作用在于創(chuàng)建對象時(shí)為對象的成員屬性賦值,構(gòu)造函數(shù)由編譯器自動調(diào)用,無須手動調(diào)用;析構(gòu)函數(shù)主要作用在于對象銷毀前系統(tǒng)自動調(diào)用,執(zhí)行一 些清理工作2022-09-09