C++學(xué)習(xí)之移動(dòng)語(yǔ)義與智能指針詳解
移動(dòng)語(yǔ)義
1.幾個(gè)基本概念的理解
(1)可以取地址的是左值,不能取地址的就是右值,右值可能存在寄存器,也可能存在于棧上(短暫存在棧)上
(2)右值包括:臨時(shí)對(duì)象、匿名對(duì)象、字面值常量
(3)const 左值引用可以綁定到左值與右值上面,稱為萬(wàn)能引用。正因如此,也就無(wú)法區(qū)分傳進(jìn)來(lái)的參數(shù)是左值還是右值。
const int &ref = a;//const左值引用可以綁定到左值 const int &ref1 = 10;//const左值引用可以綁定到右值
(4)右值引用:只能綁定到右值不能綁定到左值
2.移動(dòng)構(gòu)造函數(shù)
注意:
移動(dòng)函數(shù)(移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符函數(shù))優(yōu)先于復(fù)制函數(shù)(拷貝構(gòu)造函數(shù)和賦值運(yùn)算符函數(shù))的執(zhí)行;具有移動(dòng)語(yǔ)義的函數(shù)(移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符函數(shù))優(yōu)先于具有復(fù)制控制語(yǔ)義函數(shù)(拷貝構(gòu)造函數(shù)和賦值運(yùn)算符函數(shù))的執(zhí)行
String(String &&rhs) : _pstr(rhs._pstr) { cout << "String(String &&)" << endl; rhs._pstr = nullptr; }
3.移動(dòng)賦值函數(shù)
String &operator=(String &&rhs) { cout << "String &operator=(String &&)" << endl; if(this != &rhs)//1、自移動(dòng) { delete [] _pstr;//2、釋放左操作數(shù) _pstr = nullptr; _pstr = rhs._pstr;//3、淺拷貝 rhs._pstr = nullptr; } return *this;//4、返回*this }
4.std::move函數(shù)
std::move:
原理:將左值轉(zhuǎn)換為右值,在內(nèi)部其實(shí)上是做了一個(gè)強(qiáng)制轉(zhuǎn)換,static_cast<T &&>(lvaule)。將左值轉(zhuǎn)換為右值后,左值就不能直接使用了,如果還想繼續(xù)使用,必須重新賦值。std::move()作用于內(nèi)置類(lèi)型沒(méi)有任何作用,內(nèi)置類(lèi)型本身是左值還是右值,經(jīng)過(guò)std::move()后不會(huì)改變。
5.面試題,關(guān)于實(shí)現(xiàn)String
#include <iostream> #include <string> using std::string; using std::cout; using std::endl; class String { public: //(當(dāng)傳遞右值的時(shí)候)具有移動(dòng)語(yǔ)義的函數(shù)優(yōu)先于具有復(fù)制控制語(yǔ)義的函數(shù) //移動(dòng)構(gòu)造函數(shù)(只針對(duì)右值) String(String &&rhs) : _pstr(rhs._pstr) { cout << "String(String &&)" << endl; rhs._pstr = nullptr; } //移動(dòng)賦值運(yùn)算符函數(shù)(傳入右值) String &operator=(String &&rhs) { cout << "String &operator=(String &&)" << endl; if(this!=&rhs){ //不能自復(fù)制 delete [] _pstr;//釋放左操作數(shù) _pstr = nullptr; _pstr=rhs._pstr;//轉(zhuǎn)移右操作的資源 rhs._pstr=nullptr;//釋放右值 } return *this; } private: char* _pstr; };
資源管理和智能指針
一、C語(yǔ)言中的問(wèn)題
C語(yǔ)言在對(duì)資源管理的時(shí)候,比如文件指針,由于分支較多,或者由于寫(xiě)代碼的人與維護(hù)的人不一致,導(dǎo)致分支沒(méi)有寫(xiě)的那么完善,從而導(dǎo)致文件指針沒(méi)有釋放,所以可以使用C++的方式管理文件指針。。。
class SafeFile { public: //在構(gòu)造的時(shí)候托管資源(fp) SafeFile(FILE *fp) : _fp(fp) { cout << "SafeFile(FILE *)" << endl; if(nullptr==fp) { cout << "nullptr == _fp " << endl; } } //提供若干訪問(wèn)資源的方法 void write(const string &msg) { fwrite(msg.c_str(),1,msg.size(),_fp); //調(diào)用c語(yǔ)言的函數(shù)往_fp輸入數(shù)據(jù) } //在銷(xiāo)毀(析構(gòu))時(shí)候釋放資源(fp) ~SafeFile() { cout << "~SafeFile()" << endl; if(_fp) { fclose(_fp); cout << "fclose(_fp)" << endl; } } private: FILE *_fp; }; void test() { string s1 = "hello,world\n"; SafeFile sf(fopen("text.txt","a+")); sf.write(s1); }
二、C++的解決辦法(RAII技術(shù))
1)概念:資源管理 RAII 技術(shù),利用對(duì)象的生命周期管理程序資源(包括內(nèi)存、文件句柄、鎖等)的技術(shù),因?yàn)閷?duì)象在離開(kāi)作用域的時(shí)候,會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù)
2)關(guān)鍵:要保證資源的釋放順序與獲取順序嚴(yán)格相反。。正好是析構(gòu)函數(shù)與構(gòu)造函數(shù)的作用
3)RAII常見(jiàn)特征
1、在構(gòu)造時(shí)初始化資源,或者托管資源。
2、析構(gòu)時(shí)釋放資源。
3、一般不允許復(fù)制或者賦值(值語(yǔ)義-對(duì)象語(yǔ)義)
4、提供若干訪問(wèn)資源的方法。
4)區(qū)分:值語(yǔ)義:可以進(jìn)行復(fù)制與賦值。
5)對(duì)象語(yǔ)義:不能進(jìn)行復(fù)制與賦值,一般使用兩種方法達(dá)到要求:
(1)、將拷貝構(gòu)造函數(shù)和賦值運(yùn)算符函數(shù)設(shè)置為私有的就 ok 。
(2)、將拷貝構(gòu)造函數(shù)和賦值運(yùn)算符函數(shù)使用=delete.
6)RAII技術(shù)代碼
template <typename T> class RAII { public: //通過(guò)構(gòu)造函數(shù)托管資源 RAII(T *data) : _data(data) { std::cout<< "RAII(T *)" << std::endl; } //訪問(wèn)資源的方法 T *operator->() { return _data; } T &operator*() { return *_data; } T *get()const { return _data; } void reset(T *data) { if(_data) { delete _data; _data = nullptr; } _data = data; } //不能賦值和復(fù)制 RAII(const RAII&rhs) = delete; RAII&operator=(const RAII&rhs)=delete; //通過(guò)析構(gòu)函數(shù)釋放資源 ~RAII() { cout << "~RAII()" << endl; if(_data) { delete _data; _data = nullptr; } } private: T *_data; }; void test3() { //這里沒(méi)給出Point類(lèi)的實(shí)現(xiàn)方式 RAII<Point> ppt(new Point(1,2)); cout<<"ppt = "; ppt->print(); cout<<endl; }
三、四種智能指針
RAII的對(duì)象ppt就有智能指針的雛形。
1、auto_ptr.cc
最簡(jiǎn)單的智能指針,使用上存在缺陷,所以被棄用。。。(C++17已經(jīng)將其刪除了)
2、unique_ptr
比auto_ptr安全多了,明確表明是獨(dú)享所有權(quán)的智能指針,所以不能進(jìn)行復(fù)制與賦值。
unique_ptr<int> up(new int(10)); cout<<"*up="<<*up<<endl; //打印10 cout<<"up.get() = "<<up.get()<<endl; //獲取托管的指針的值,也就是10的地址 cout << endl << endl; /* unique_ptr<int> up2(up);//error,獨(dú)享資源的所有權(quán),不能進(jìn)行復(fù)制 */ unique_ptr<int> up4(std::move(up)); //通過(guò)移動(dòng)語(yǔ)義轉(zhuǎn)移up的所有權(quán) cout<<"*up="<<*up4<<endl; cout<<"up.get() = "<<up4.get()<<endl; unique_ptr<Point> up5(new Point(3,4));//通過(guò)移動(dòng)語(yǔ)義轉(zhuǎn)移up的所有權(quán) vector<unique_ptr<Point>> numbers; numbers.push_back(unique_ptr<Point>(new Point(1,2))); numbers.push_back(std::move(up5));
3、shared_ptr
shared_ptr<int> sp(new int(10)); cout << "*sp = " << *sp << endl; //打印10 cout << "sp.get() = " << sp.get() << endl; //地址 cout << "sp.use_count() = " << sp.use_count() << endl; //引用次數(shù)為1 cout<<endl<<endl; //提前結(jié)束棧對(duì)象 { shared_ptr<int> sp2(sp);//共享所有權(quán),使用淺拷貝 cout << "*sp = " << *sp << endl; cout << "sp.get() = " << sp.get() << endl; cout << "sp.use_count() = " << sp.use_count() << endl; cout << "*sp2 = " << *sp2 << endl; cout << "sp2.get() = " << sp2.get() << endl; //地址都一樣 cout << "sp2.use_count() = " << sp2.use_count() << endl; //引用次數(shù)加1,變?yōu)?了 } cout << "sp.use_count() = " << sp.use_count() << endl; //又變?yōu)?了 cout << endl << endl; shared_ptr<Point> sp4(new Point(3.4));//通過(guò)移動(dòng)語(yǔ)義轉(zhuǎn)移sp的所有權(quán) vector<shared_ptr<Point>> numbers; numbers.push_back(shared_ptr<Point> (new Point(1,2))); numbers.push_back(sp4); numbers[0]->print(); numbers[1]->print();
3.1、循環(huán)引用
該智能指針在使用的時(shí)候,會(huì)使得引用計(jì)數(shù)增加,從而會(huì)出現(xiàn)循環(huán)引用的問(wèn)題,兩個(gè)shared_ptr智能指針互指,導(dǎo)致引用計(jì)數(shù)增加,不能靠對(duì)象的銷(xiāo)毀使得引用計(jì)數(shù)變?yōu)?,從而導(dǎo)致內(nèi)存泄漏。。
class Child; class Parent { public: Parent() { cout << "Parent()" << endl; } ~Parent() { cout << "~Parent()" << endl; } shared_ptr<Child> pParent; }; class Child { public: Child() { cout << "Child()" << endl; } ~Child() { cout << "~Child()" << endl; } shared_ptr<Parent> pChild; }; void test() { //循環(huán)引用可能導(dǎo)致內(nèi)存泄漏 shared_ptr<Parent> parentPtr(new Parent()); shared_ptr<Child> childPtr(new Child()); cout << "parentPtr.use_count() = " << parentPtr.use_count() << endl; cout << "childPtr.use_count() = " << childPtr.use_count() << endl; cout << endl << endl; parentPtr->pParent = childPtr;//sp = sp childPtr->pChild = parentPtr; cout << "parentPtr.use_count() = " << parentPtr.use_count() << endl; cout << "childPtr.use_count() = " << childPtr.use_count() << endl; }
1.解決循環(huán)引用的辦法是使得其中一個(gè)改為weak_ptr,不會(huì)增加引用計(jì)數(shù),這樣可以使用對(duì)象的銷(xiāo)毀而打破引用計(jì)數(shù)減為0的問(wèn)題。。
2.修改: shared_ptr pChild;改為 weak_ptr pChild;即可解決循環(huán)引用的問(wèn)題。。
parentPtr->pParent = childPtr;//sp = sp childPtr->pChild = parentPtr;//wp = sp,weak_ptr不會(huì)導(dǎo)致引用計(jì)數(shù)加1
4、weak_ptr
與shared_ptr相比,稱為弱引用的智能指針,shared_ptr是強(qiáng)引用的智能指針。weak_ptr不會(huì)導(dǎo)致引用計(jì)數(shù)增加,但是它不能直接獲取資源,必須通過(guò)lock函數(shù)從wp提升為sp,從而判斷共享的資源是否已經(jīng)銷(xiāo)毀
weak_ptr<Point> wp { shared_ptr<Point> sp(new Point(1,2)); wp = sp; cout << "wp.use_count = " << wp.use_count() << endl; cout << "sp.use_count = " << sp.use_count() << endl; cout<<"wp.expired = "<<wp.expired()<<endl;//此方法等同于use_count()==0? //等于0表示false,空間還存在 //不等0表示true,空間已經(jīng)不存在了 //expired = use_count shared_ptr<Point> sp2 = wp.lock();//判斷共享的資源是否已經(jīng)銷(xiāo)毀的方式就是從wp提升為sp if(sp2) { cout << "提升成功" << endl; } else { cout << "提升失敗" << endl; } }
四、為智能指針定制刪除器
1)很多時(shí)候我們都用new來(lái)申請(qǐng)空間,用delete來(lái)釋放。庫(kù)中實(shí)現(xiàn)的各種智能指針,默認(rèn)也都是用delete來(lái)釋放空間,但是若我們采用malloc申請(qǐng)的空間或是用fopen打開(kāi)的文件,這時(shí)我們的智能指針就無(wú)法來(lái)處理,因此我們需要為智能指針定制刪除器,提供一個(gè)可以自由選擇析構(gòu)的接口,這樣,我們的智能指針就可以處理不同形式開(kāi)辟的空間以及可以管理文件指針。
2)自定義智能指針的方式有兩種:
(1)函數(shù)指針
(2)仿函數(shù)(函數(shù)對(duì)象)
函數(shù)指針的形式:
template<class T> void Free(T* p) { if (p) free(p); } template<class T> void Del(T* p) { if (p) delete p; } void FClose(FILE* pf) { if (pf) fclose(pf); } //定義函數(shù)指針的類(lèi)型 typedef void(*DP)(void*); template<class T> class SharedPtr { public: SharedPtr(T* ptr = NULL ,DP dp=Del) :_ptr(ptr) , _pCount(NULL) , _dp(dp) { if (_ptr != NULL) { _pCount = new int(1); } } private: void Release() { if (_ptr&&0==--GetRef()) { //delete _ptr; _dp(_ptr); delete _pCount; } } int& GetRef() { return *_pCount; } private: T* _ptr; int* _pCount; DP _dp; };
仿函數(shù)(函數(shù)對(duì)象)
關(guān)于刪除器的使用
五、智能指針的誤用
1、同一個(gè)裸指針被不同的智能指針托管,導(dǎo)致被析構(gòu)兩次。
1.1、直接使用
1.2、間接使用
2、還是裸指針被智能指針托管形式,但是比較隱蔽。。。
class Point : public std::enable_shared_from_this<Point> { public: Point(int ix = 0, int iy = 0) : _ix(ix) , _iy(iy) { cout << "Point(int = 0, int = 0)" << endl; } void print() const { cout << "(" <<_ix << "," << _iy << ")" << endl; } /* Point *addPoint(Point *pt) */ shared_ptr<Point> addPoint(Point *pt) { _ix += pt->_ix; _iy += pt->_iy; //this指針是一個(gè)裸指針 /* return shared_ptr<Point>(this); */ return shared_from_this(); } ~Point() { cout << "~Point()" << endl; } private: int _ix; int _iy; }; void test3() { shared_ptr<Point> sp1(new Point(1, 2)); cout << "sp1 = "; sp1->print(); cout << endl; shared_ptr<Point> sp2(new Point(3, 4)); cout << "sp2 = "; sp2->print(); cout << endl; shared_ptr<Point> sp3(sp1->addPoint(sp2.get())); cout << "sp3 = "; sp3->print(); }
總結(jié)
到此這篇關(guān)于C++學(xué)習(xí)之移動(dòng)語(yǔ)義與智能指針的文章就介紹到這了,更多相關(guān)C++移動(dòng)語(yǔ)義與智能指針內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng)項(xiàng)目
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng)項(xiàng)目,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01opencv利用鼠標(biāo)滑動(dòng)畫(huà)出多彩的形狀
這篇文章主要為大家詳細(xì)介紹了opencv利用鼠標(biāo)滑動(dòng)畫(huà)出多彩的形狀,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-07-07C++線性表深度解析之動(dòng)態(tài)數(shù)組與單鏈表和棧及隊(duì)列的實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)動(dòng)態(tài)數(shù)組、單鏈表、棧、隊(duì)列,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05C語(yǔ)言函數(shù)基礎(chǔ)教程分類(lèi)自定義參數(shù)及調(diào)用示例詳解
這篇文章主要為大家介紹了C語(yǔ)言函數(shù)的基礎(chǔ)教程,主要包含C語(yǔ)言函數(shù)的分類(lèi),C語(yǔ)言函數(shù)自定義,C語(yǔ)言函數(shù)的參數(shù)及C語(yǔ)言函數(shù)的調(diào)用示例詳解,有需要的朋友可以借鑒參考下2021-11-11C語(yǔ)言數(shù)據(jù)結(jié)構(gòu) 棧的基礎(chǔ)操作
這篇文章主要介紹了C語(yǔ)言數(shù)據(jù)結(jié)構(gòu) 棧的基礎(chǔ)操作的相關(guān)資料,需要的朋友可以參考下2017-05-05Qt實(shí)現(xiàn)模糊匹配功能的實(shí)例詳解
對(duì)于瀏覽器的使用,我想大家一定不會(huì)陌生吧,輸入要搜索的內(nèi)容時(shí),會(huì)出現(xiàn)相應(yīng)的匹配信息。本文就來(lái)用Qt實(shí)現(xiàn)模糊匹配功能,感興趣的可以了解一下2022-10-10詳解Dijkstra算法原理及其C++實(shí)現(xiàn)
Dijkstra算法用于計(jì)算一個(gè)節(jié)點(diǎn)到其他節(jié)點(diǎn)的最短路徑。Dijkstra是一種按路徑長(zhǎng)度遞增的順序逐步產(chǎn)生最短路徑的方法,是一種貪婪算法。本文將詳解Dijkstra算法原理及其C++實(shí)現(xiàn),感興趣的可以了解一下2022-07-07