一文掌握C++ 智能指針全部用法
為什么要學(xué)習(xí)智能指針?
咳咳,這個(gè)問(wèn)題不是問(wèn)大家的,是詢問(wèn)我自己的!
我依稀記得剛離校出來(lái)找實(shí)習(xí)工作那會(huì),去面試一份工作,其中有一個(gè)環(huán)節(jié)需要答題;有一道題目就是問(wèn)什么是智能指針?臥槽?當(dāng)時(shí)我就懵逼,智能指針我壓根就沒(méi)有聽(tīng)說(shuō)過(guò)…
最后,面試的這份工作理所應(yīng)當(dāng)?shù)狞S了。
差不多是一年前左右吧,現(xiàn)在趁有閑余時(shí)間,學(xué)習(xí)一下智能指針,豐富一下自己!
一、為什么要使用智能指針
一句話帶過(guò):智能指針就是幫我們C++程序員管理動(dòng)態(tài)分配的內(nèi)存的,它會(huì)幫助我們自動(dòng)釋放new出來(lái)的內(nèi)存,從而避免內(nèi)存泄漏!
如下例子就是內(nèi)存泄露的例子:
#include <iostream> #include <string> #include <memory> using namespace std; // 動(dòng)態(tài)分配內(nèi)存,沒(méi)有釋放就return void memoryLeak1() { string *str = new string("動(dòng)態(tài)分配內(nèi)存!"); return; } // 動(dòng)態(tài)分配內(nèi)存,雖然有些釋放內(nèi)存的代碼,但是被半路截胡return了 int memoryLeak2() { string *str = new string("內(nèi)存泄露!"); // ...此處省略一萬(wàn)行代碼 // 發(fā)生某些異常,需要結(jié)束函數(shù) if (1) { return -1; } delete str; // 雖然寫(xiě)了釋放內(nèi)存的代碼,但是遭到函數(shù)中段返回,使得指針沒(méi)有得到釋放 return 1; } int main(void) { memoryLeak1(); memoryLeak2(); return 0; }
memoryLeak1函數(shù)中,new了一個(gè)字符串指針,但是沒(méi)有delete就已經(jīng)return結(jié)束函數(shù)了,導(dǎo)致內(nèi)存沒(méi)有被釋放,內(nèi)存泄露!
memoryLeak2函數(shù)中,new了一個(gè)字符串指針,雖然在函數(shù)末尾有些釋放內(nèi)存的代碼delete str,但是在delete之前就已經(jīng)return了,所以內(nèi)存也沒(méi)有被釋放,內(nèi)存泄露!
使用指針,我們沒(méi)有釋放,就會(huì)造成內(nèi)存泄露。但是我們使用普通對(duì)象卻不會(huì)!
思考:如果我們分配的動(dòng)態(tài)內(nèi)存都交由有生命周期的對(duì)象來(lái)處理,那么在對(duì)象過(guò)期時(shí),讓它的析構(gòu)函數(shù)刪除指向的內(nèi)存,這看似是一個(gè) very nice 的方案?
智能指針就是通過(guò)這個(gè)原理來(lái)解決指針自動(dòng)釋放的問(wèn)題!
- C++98 提供了 auto_ptr 模板的解決方案
- C++11 增加unique_ptr、shared_ptr 和weak_ptr
二、auto_ptr
auto_ptr 是c++ 98定義的智能指針模板,其定義了管理指針的對(duì)象,可以將new 獲得(直接或間接)的地址賦給這種對(duì)象。當(dāng)對(duì)象過(guò)期時(shí),其析構(gòu)函數(shù)將使用delete 來(lái)釋放內(nèi)存!
用法:
頭文件: #include < memory >
用 法: auto_ptr<類(lèi)型> 變量名(new 類(lèi)型)
例 如:
auto_ptr< string > str(new string(“我要成為大牛~ 變得很牛逼!”));
auto_ptr<vector< int >> av(new vector< int >());
auto_ptr< int > array(new int[10]);
例:
我們先定義一個(gè)類(lèi),類(lèi)的構(gòu)造函數(shù)和析構(gòu)函數(shù)都輸出一個(gè)字符串用作提示!
定義一個(gè)私有成員變量,賦值20.
再定義一個(gè)私有成員方法用于返回這個(gè)私有成員變量。
class Test { public: Test() { cout << "Test的構(gòu)造函數(shù)..." << endl; } ~Test() { cout << "Test的析構(gòu)函數(shù)..." << endl; } int getDebug() { return this->debug; } private: int debug = 20; };
當(dāng)我們直接new這個(gè)類(lèi)的對(duì)象,卻沒(méi)有釋放時(shí)。。。
int main(void) { Test *test = new Test; return 0; }
可以看到,只是打印了構(gòu)造函數(shù)這個(gè)字符串,而析構(gòu)函數(shù)的字符卻沒(méi)有被打印,說(shuō)明并沒(méi)有調(diào)用析構(gòu)函數(shù)!這就導(dǎo)致了內(nèi)存泄露!
解決內(nèi)存泄露的辦法,要么手動(dòng)delete,要么使用智能指針!
使用智能指針:
// 定義智能指針 auto_ptr<Test> test(new Test);
智能指針可以像普通指針那樣使用:
cout << "test->debug:" << test->getDebug() << endl; cout << "(*test).debug:" << (*test).getDebug() << endl;
這時(shí)再試試:
int main(void) { //Test *test = new Test; auto_ptr<Test> test(new Test); cout << "test->debug:" << test->getDebug() << endl; cout << "(*test).debug:" << (*test).getDebug() << endl; return 0; }
自動(dòng)調(diào)用了析構(gòu)函數(shù)。
為什么智能指針可以像普通指針那樣使用???
因?yàn)槠淅锩嬷剌d了 * 和 -> 運(yùn)算符, * 返回普通對(duì)象,而 -> 返回指針對(duì)象。
具體原因不用深究,只需知道他為什么可以這樣操作就像!
函數(shù)中返回的是調(diào)用get()方法返回的值,那么這個(gè)get()是什么呢?
智能指針的三個(gè)常用函數(shù):
get() 獲取智能指針托管的指針地址
// 定義智能指針 auto_ptr<Test> test(new Test); Test *tmp = test.get(); // 獲取指針?lè)祷? cout << "tmp->debug:" << tmp->getDebug() << endl;
但我們一般不會(huì)這樣使用,因?yàn)槎伎梢灾苯邮褂弥悄苤羔樔ゲ僮?,除非有一些特殊情況。
函數(shù)原型:
_NODISCARD _Ty * get() const noexcept { // return wrapped pointer return (_Myptr); }
release() 取消智能指針對(duì)動(dòng)態(tài)內(nèi)存的托管
// 定義智能指針 auto_ptr<Test> test(new Test); Test *tmp2 = test.release(); // 取消智能指針對(duì)動(dòng)態(tài)內(nèi)存的托管 delete tmp2; // 之前分配的內(nèi)存需要自己手動(dòng)釋放
也就是智能指針不再對(duì)該指針進(jìn)行管理,改由管理員進(jìn)行管理!
函數(shù)原型:
_Ty * release() noexcept { // return wrapped pointer and give up ownership _Ty * _Tmp = _Myptr; _Myptr = nullptr; return (_Tmp); }
reset() 重置智能指針托管的內(nèi)存地址,如果地址不一致,原來(lái)的會(huì)被析構(gòu)掉
// 定義智能指針 auto_ptr<Test> test(new Test); test.reset(); // 釋放掉智能指針托管的指針內(nèi)存,并將其置NULL test.reset(new Test()); // 釋放掉智能指針托管的指針內(nèi)存,并將參數(shù)指針取代之
reset函數(shù)會(huì)將參數(shù)的指針(不指定則為NULL),與托管的指針比較,如果地址不一致,那么就會(huì)析構(gòu)掉原來(lái)托管的指針,然后使用參數(shù)的指針替代之。然后智能指針就會(huì)托管參數(shù)的那個(gè)指針了。
函數(shù)原型:
void reset(_Ty * _Ptr = nullptr) { // destroy designated object and store new pointer if (_Ptr != _Myptr) delete _Myptr; _Myptr = _Ptr; }
使用建議:
盡可能不要將auto_ptr 變量定義為全局變量或指針;
// 沒(méi)有意義,全局變量也是一樣 auto_ptr<Test> *tp = new auto_ptr<Test>(new Test);
除非自己知道后果,不要把a(bǔ)uto_ptr 智能指針賦值給同類(lèi)型的另外一個(gè) 智能指針;
auto_ptr<Test> t1(new Test); auto_ptr<Test> t2(new Test); t1 = t2; // 不要這樣操作...
C++11 后auto_ptr 已經(jīng)被“拋棄”,已使用unique_ptr替代!C++11后不建議使用auto_ptr。
auto_ptr 被C++11拋棄的主要原因
1). 復(fù)制或者賦值都會(huì)改變資源的所有權(quán)
// auto_ptr 被C++11拋棄的主要原因 auto_ptr<string> p1(new string("I'm Li Ming!")); auto_ptr<string> p2(new string("I'm age 22.")); cout << "p1:" << p1.get() << endl; cout << "p2:" << p2.get() << endl; // p2賦值給p1后,首先p1會(huì)先將自己原先托管的指針釋放掉,然后接收托管p2所托管的指針, // 然后p2所托管的指針制NULL,也就是p1托管了p2托管的指針,而p2放棄了托管。 p1 = p2; cout << "p1 = p2 賦值后:" << endl; cout << "p1:" << p1.get() << endl; cout << "p2:" << p2.get() << endl;
2). 在STL容器中使用auto_ptr存在著重大風(fēng)險(xiǎn),因?yàn)槿萜鲀?nèi)的元素必須支持可復(fù)制和可賦值
vector<auto_ptr<string>> vec; auto_ptr<string> p3(new string("I'm P3")); auto_ptr<string> p4(new string("I'm P4")); // 必須使用std::move修飾成右值,才可以進(jìn)行插入容器中 vec.push_back(std::move(p3)); vec.push_back(std::move(p4)); cout << "vec.at(0):" << *vec.at(0) << endl; cout << "vec[1]:" << *vec[1] << endl; // 風(fēng)險(xiǎn)來(lái)了: vec[0] = vec[1]; // 如果進(jìn)行賦值,問(wèn)題又回到了上面一個(gè)問(wèn)題中。 cout << "vec.at(0):" << *vec.at(0) << endl; cout << "vec[1]:" << *vec[1] << endl;
訪問(wèn)越界了!
3). 不支持對(duì)象數(shù)組的內(nèi)存管理
auto_ptr<int[]> array(new int[5]); // 不能這樣定義
所以,C++11用更嚴(yán)謹(jǐn)?shù)膗nique_ptr 取代了auto_ptr!
測(cè)試代碼:
#include <iostream> #include <string> #include <memory> #include <vector> using namespace std; class Test { public: Test() { cout << "Test的構(gòu)造函數(shù)..." << endl; } ~Test() { cout << "Test的析構(gòu)函數(shù)..." << endl; } int getDebug() { return this->debug; } private: int debug = 20; }; // 不要定義為全局變量,沒(méi)有意義 //auto_ptr<Test> test(new Test); void memoryLeak1() { //Test *test = new Test; // 定義智能指針 auto_ptr<Test> test(new Test); cout << "test->debug:" << test->getDebug() << endl; cout << "(*test).debug:" << (*test).getDebug() << endl; // get方法 Test *tmp = test.get(); // 獲取指針?lè)祷? cout << "tmp->debug:" << tmp->getDebug() << endl; // release方法 Test *tmp2 = test.release(); // 取消智能指針對(duì)動(dòng)態(tài)內(nèi)存的托管 delete tmp2; // 之前分配的內(nèi)存需要自己手動(dòng)釋放 // reset方法:重置智能指針托管的內(nèi)存地址,如果地址不一致,原來(lái)的會(huì)被析構(gòu)掉 test.reset(); // 釋放掉智能指針托管的指針內(nèi)存,并將其置NULL test.reset(new Test()); // 釋放掉智能指針托管的指針內(nèi)存,并將參數(shù)指針取代之 // 忠告:不要將智能指針定義為指針 //auto_ptr<Test> *tp = new auto_ptr<Test>(new Test); // 忠告:不要定義指向智能指針對(duì)象的指針變量 //auto_ptr<Test> t1(new Test); //auto_ptr<Test> t2(new Test); //t1 = t2; return; } int memoryLeak2() { //Test *test = new Test(); // 定義智能指針 auto_ptr<Test> test(new Test); // ...此處省略一萬(wàn)行代碼 // 發(fā)生某些異常,需要結(jié)束函數(shù) if (1) { return -1; } //delete test; return 1; } int main1(void) { //memoryLeak1(); //memoryLeak2(); //Test *test = new Test; //auto_ptr<Test> test(new Test); //cout << "test->debug:" << test->getDebug() << endl; //cout << "(*test).debug:" << (*test).getDebug() << endl; auto_ptr 被C++11拋棄的主要原因 //auto_ptr<string> p1(new string("I'm Li Ming!")); //auto_ptr<string> p2(new string("I'm age 22.")); // //cout << "p1:" << p1.get() << endl; //cout << "p2:" << p2.get() << endl; //p1 = p2; //cout << "p1 = p2 賦值后:" << endl; //cout << "p1:" << p1.get() << endl; //cout << "p2:" << p2.get() << endl; // 弊端2.在STL容器中使用auto_ptr存在著重大風(fēng)險(xiǎn),因?yàn)槿萜鲀?nèi)的元素必須支持可復(fù)制 vector<auto_ptr<string>> vec; auto_ptr<string> p3(new string("I'm P3")); auto_ptr<string> p4(new string("I'm P4")); vec.push_back(std::move(p3)); vec.push_back(std::move(p4)); cout << "vec.at(0):" << *vec.at(0) << endl; cout << "vec[1]:" << *vec[1] << endl; // 風(fēng)險(xiǎn)來(lái)了: vec[0] = vec[1]; cout << "vec.at(0):" << *vec.at(0) << endl; cout << "vec[1]:" << *vec[1] << endl; // 弊端3.不支持對(duì)象數(shù)組的內(nèi)存管理 //auto_ptr<int[]> array(new int[5]); // 不能這樣定義 return 0; }
三、unique_ptr
auto_ptr是用于C++11之前的智能指針。由于 auto_ptr 基于排他所有權(quán)模式:兩個(gè)指針不能指向同一個(gè)資源,復(fù)制或賦值都會(huì)改變資源的所有權(quán)。auto_ptr 主要有三大問(wèn)題:
- 復(fù)制和賦值會(huì)改變資源的所有權(quán),不符合人的直覺(jué)。
- 在 STL 容器中使用auto_ptr存在重大風(fēng)險(xiǎn),因?yàn)槿萜鲀?nèi)的元素必需支持可復(fù)制(copy constructable)和可賦值(assignable)。
- 不支持對(duì)象數(shù)組的操作
以上問(wèn)題已經(jīng)在上面體現(xiàn)出來(lái)了,下面將使用unique_ptr解決這些問(wèn)題。
所以,C++11用更嚴(yán)謹(jǐn)?shù)膗nique_ptr 取代了auto_ptr!
unique_ptr 和 auto_ptr用法幾乎一樣,除了一些特殊。
unique_ptr特性
- 基于排他所有權(quán)模式:兩個(gè)指針不能指向同一個(gè)資源
- 無(wú)法進(jìn)行左值unique_ptr復(fù)制構(gòu)造,也無(wú)法進(jìn)行左值復(fù)制賦值操作,但允許臨時(shí)右值賦值構(gòu)造和賦值
- 保存指向某個(gè)對(duì)象的指針,當(dāng)它本身離開(kāi)作用域時(shí)會(huì)自動(dòng)釋放它指向的對(duì)象。
- 在容器中保存指針是安全的
A. 無(wú)法進(jìn)行左值復(fù)制賦值操作,但允許臨時(shí)右值賦值構(gòu)造和賦值
unique_ptr<string> p1(new string("I'm Li Ming!")); unique_ptr<string> p2(new string("I'm age 22.")); cout << "p1:" << p1.get() << endl; cout << "p2:" << p2.get() << endl; p1 = p2; // 禁止左值賦值 unique_ptr<string> p3(p2); // 禁止左值賦值構(gòu)造 unique_ptr<string> p3(std::move(p1)); p1 = std::move(p2); // 使用move把左值轉(zhuǎn)成右值就可以賦值了,效果和auto_ptr賦值一樣 cout << "p1 = p2 賦值后:" << endl; cout << "p1:" << p1.get() << endl; cout << "p2:" << p2.get() << endl;
運(yùn)行截圖:
B. 在 STL 容器中使用unique_ptr,不允許直接賦值
vector<unique_ptr<string>> vec; unique_ptr<string> p3(new string("I'm P3")); unique_ptr<string> p4(new string("I'm P4")); vec.push_back(std::move(p3)); vec.push_back(std::move(p4)); cout << "vec.at(0):" << *vec.at(0) << endl; cout << "vec[1]:" << *vec[1] << endl; vec[0] = vec[1]; /* 不允許直接賦值 */ vec[0] = std::move(vec[1]); // 需要使用move修飾,使得程序員知道后果 cout << "vec.at(0):" << *vec.at(0) << endl; cout << "vec[1]:" << *vec[1] << endl;
當(dāng)然,運(yùn)行后是直接報(bào)錯(cuò)的,因?yàn)関ec[1]已經(jīng)是NULL了,再繼續(xù)訪問(wèn)就越界了。
C. 支持對(duì)象數(shù)組的內(nèi)存管理
// 會(huì)自動(dòng)調(diào)用delete [] 函數(shù)去釋放內(nèi)存 unique_ptr<int[]> array(new int[5]); // 支持這樣定義
除了上面ABC三項(xiàng)外,unique_ptr的其余用法都與auto_ptr用法一致。
構(gòu)造
class Test { public: Test() { cout << "Test的構(gòu)造函數(shù)..." << endl; } ~Test() { cout << "Test的析構(gòu)函數(shù)..." << endl; } void doSomething() { cout << "do something......" << endl; } }; // 自定義一個(gè)內(nèi)存釋放其 class DestructTest { public: void operator()(Test *pt) { pt->doSomething(); delete pt; } }; // unique_ptr<T> up; 空的unique_ptr,可以指向類(lèi)型為T(mén)的對(duì)象 unique_ptr<Test> t1; // unique_ptr<T> up1(new T()); 定義unique_ptr,同時(shí)指向類(lèi)型為T(mén)的對(duì)象 unique_ptr<Test> t2(new Test); // unique_ptr<T[]> up; 空的unique_ptr,可以指向類(lèi)型為T(mén)[的數(shù)組對(duì)象 unique_ptr<int[]> t3; // unique_ptr<T[]> up1(new T[]); 定義unique_ptr,同時(shí)指向類(lèi)型為T(mén)的數(shù)組對(duì)象 unique_ptr<int[]> t4(new int[5]); // unique_ptr<T, D> up(); 空的unique_ptr,接受一個(gè)D類(lèi)型的刪除器D,使用D釋放內(nèi)存 unique_ptr<Test, DestructTest> t5; // unique_ptr<T, D> up(new T()); 定義unique_ptr,同時(shí)指向類(lèi)型為T(mén)的對(duì)象,接受一個(gè)D類(lèi)型的刪除器D,使用刪除器D來(lái)釋放內(nèi)存 unique_ptr<Test, DestructTest> t6(new Test);
賦值
unique_ptr<Test> t7(new Test); unique_ptr<Test> t8(new Test); t7 = std::move(t8); // 必須使用移動(dòng)語(yǔ)義,結(jié)果,t7的內(nèi)存釋放,t8的內(nèi)存交給t7管理 t7->doSomething();
主動(dòng)釋放對(duì)象
unique_ptr<Test> t9(new Test); t9 = NULL; t9 = nullptr; t9.reset();
放棄對(duì)象的控制權(quán)
Test *t10 = t9.release();
重置
t9.reset(new Test);
auto_ptr 與 unique_ptr智能指針的內(nèi)存管理陷阱
auto_ptr<string> p1; string *str = new string("智能指針的內(nèi)存管理陷阱"); p1.reset(str); // p1托管str指針 { auto_ptr<string> p2; p2.reset(str); // p2接管str指針時(shí),會(huì)先取消p1的托管,然后再對(duì)str的托管 } // 此時(shí)p1已經(jīng)沒(méi)有托管內(nèi)容指針了,為NULL,在使用它就會(huì)內(nèi)存報(bào)錯(cuò)! cout << "str:" << *p1 << endl;
這是由于auto_ptr 與 unique_ptr的排他性所導(dǎo)致的!
為了解決這樣的問(wèn)題,我們可以使用shared_ptr指針指針!
四、shared_ptr
熟悉了unique_ptr 后,其實(shí)我們發(fā)現(xiàn)unique_ptr 這種排他型的內(nèi)存管理并不能適應(yīng)所有情況,有很大的局限!如果需要多個(gè)指針變量共享怎么辦?
如果有一種方式,可以記錄引用特定內(nèi)存對(duì)象的智能指針數(shù)量,當(dāng)復(fù)制或拷貝時(shí),引用計(jì)數(shù)加1,當(dāng)智能指針析構(gòu)時(shí),引用計(jì)數(shù)減1,如果計(jì)數(shù)為零,代表已經(jīng)沒(méi)有指針指向這塊內(nèi)存,那么我們就釋放它!這就是 shared_ptr 采用的策略!
例:
class Person { public: Person(int v) { this->no = v; cout << "構(gòu)造函數(shù) \t no = " << this->no << endl; } ~Person() { cout << "析構(gòu)函數(shù) \t no = " << this->no << endl; } private: int no; }; // 仿函數(shù),內(nèi)存刪除 class DestructPerson { public: void operator() (Person *pt) { cout << "DestructPerson..." << endl; delete pt; } };
引用計(jì)數(shù)的使用
調(diào)用use_count函數(shù)可以獲得當(dāng)前托管指針的引用計(jì)數(shù)。
shared_ptr<Person> sp1; shared_ptr<Person> sp2(new Person(2)); // 獲取智能指針管控的共享指針的數(shù)量 use_count():引用計(jì)數(shù) cout << "sp1 use_count() = " << sp1.use_count() << endl; cout << "sp2 use_count() = " << sp2.use_count() << endl << endl; // 共享 sp1 = sp2; cout << "sp1 use_count() = " << sp1.use_count() << endl; cout << "sp2 use_count() = " << sp2.use_count() << endl << endl; shared_ptr<Person> sp3(sp1); cout << "sp1 use_count() = " << sp1.use_count() << endl; cout << "sp2 use_count() = " << sp2.use_count() << endl; cout << "sp2 use_count() = " << sp3.use_count() << endl << endl;
如上代碼,sp1 = sp2; 和 shared_ptr< Person > sp3(sp1);就是在使用引用計(jì)數(shù)了。
sp1 = sp2; --> sp1和sp2共同托管同一個(gè)指針,所以他們的引用計(jì)數(shù)為2;
shared_ptr< Person > sp3(sp1); --> sp1和sp2和sp3共同托管同一個(gè)指針,所以他們的引用計(jì)數(shù)為3;
構(gòu)造
1). shared_ptr< T > sp1; 空的shared_ptr,可以指向類(lèi)型為T(mén)的對(duì)象
shared_ptr<Person> sp1; Person *person1 = new Person(1); sp1.reset(person1); // 托管person1
2). shared_ptr< T > sp2(new T()); 定義shared_ptr,同時(shí)指向類(lèi)型為T(mén)的對(duì)象
shared_ptr<Person> sp2(new Person(2)); shared_ptr<Person> sp3(sp1);
3). shared_ptr<T[]> sp4; 空的shared_ptr,可以指向類(lèi)型為T(mén)[]的數(shù)組對(duì)象 C++17后支持
shared_ptr<Person[]> sp4;
4). shared_ptr<T[]> sp5(new T[] { … }); 指向類(lèi)型為T(mén)的數(shù)組對(duì)象 C++17后支持
shared_ptr<Person[]> sp5(new Person[5] { 3, 4, 5, 6, 7 });
5). shared_ptr< T > sp6(NULL, D()); //空的shared_ptr,接受一個(gè)D類(lèi)型的刪除器,使用D釋放內(nèi)存
shared_ptr<Person> sp6(NULL, DestructPerson());
6). shared_ptr< T > sp7(new T(), D()); //定義shared_ptr,指向類(lèi)型為T(mén)的對(duì)象,接受一個(gè)D類(lèi)型的刪除器,使用D刪除器來(lái)釋放內(nèi)存
shared_ptr<Person> sp7(new Person(8), DestructPerson());
初始化
1). 方式一:構(gòu)造函數(shù)
shared_ptr<int> up1(new int(10)); // int(10) 的引用計(jì)數(shù)為1 shared_ptr<int> up2(up1); // 使用智能指針up1構(gòu)造up2, 此時(shí)int(10) 引用計(jì)數(shù)為2
2). 方式二:使用make_shared 初始化對(duì)象,分配內(nèi)存效率更高(推薦使用)
make_shared函數(shù)的主要功能是在動(dòng)態(tài)內(nèi)存中分配一個(gè)對(duì)象并初始化它,返回指向此對(duì)象的shared_ptr; 用法:
make_shared<類(lèi)型>(構(gòu)造類(lèi)型對(duì)象需要的參數(shù)列表);
shared_ptr<int> up3 = make_shared<int>(2); // 多個(gè)參數(shù)以逗號(hào)','隔開(kāi),最多接受十個(gè) shared_ptr<string> up4 = make_shared<string>("字符串"); shared_ptr<Person> up5 = make_shared<Person>(9);
賦值
shared_ptrr<int> up1(new int(10)); // int(10) 的引用計(jì)數(shù)為1 shared_ptr<int> up2(new int(11)); // int(11) 的引用計(jì)數(shù)為1 up1 = up2; // int(10) 的引用計(jì)數(shù)減1,計(jì)數(shù)歸零內(nèi)存釋放,up2共享int(11)給up1, int(11)的引用計(jì)數(shù)為2
主動(dòng)釋放對(duì)象
shared_ptrr<int> up1(new int(10)); up1 = nullptr ; // int(10) 的引用計(jì)數(shù)減1,計(jì)數(shù)歸零內(nèi)存釋放 // 或 up1 = NULL; // 作用同上
重置
p.reset() ; 將p重置為空指針,所管理對(duì)象引用計(jì)數(shù) 減1
p.reset(p1); 將p重置為p1(的值),p 管控的對(duì)象計(jì)數(shù)減1,p接管對(duì)p1指針的管控
p.reset(p1,d); 將p重置為p1(的值),p 管控的對(duì)象計(jì)數(shù)減1并使用d作為刪除器
p1是一個(gè)指針!
交換
p1 和 p2 是智能指針
std::swap(p1,p2); // 交換p1 和p2 管理的對(duì)象,原對(duì)象的引用計(jì)數(shù)不變 p1.swap(p2); // 交換p1 和p2 管理的對(duì)象,原對(duì)象的引用計(jì)數(shù)不變
shared_ptr使用陷阱
shared_ptr作為被管控的對(duì)象的成員時(shí),小心因循環(huán)引用造成無(wú)法釋放資源!
如下代碼:
Boy類(lèi)中有Girl的智能指針;
Girl類(lèi)中有Boy的智能指針;
當(dāng)他們交叉互相持有對(duì)方的管理對(duì)象時(shí)…
#include <iostream> #include <string> #include <memory> using namespace std; class Girl; class Boy { public: Boy() { cout << "Boy 構(gòu)造函數(shù)" << endl; } ~Boy() { cout << "~Boy 析構(gòu)函數(shù)" << endl; } void setGirlFriend(shared_ptr<Girl> _girlFriend) { this->girlFriend = _girlFriend; } private: shared_ptr<Girl> girlFriend; }; class Girl { public: Girl() { cout << "Girl 構(gòu)造函數(shù)" << endl; } ~Girl() { cout << "~Girl 析構(gòu)函數(shù)" << endl; } void setBoyFriend(shared_ptr<Boy> _boyFriend) { this->boyFriend = _boyFriend; } private: shared_ptr<Boy> boyFriend; }; void useTrap() { shared_ptr<Boy> spBoy(new Boy()); shared_ptr<Girl> spGirl(new Girl()); // 陷阱用法 spBoy->setGirlFriend(spGirl); spGirl->setBoyFriend(spBoy); // 此時(shí)boy和girl的引用計(jì)數(shù)都是2 } int main(void) { useTrap(); system("pause"); return 0; }
運(yùn)行截圖:
可以看出,程序結(jié)束了,但是并沒(méi)有釋放內(nèi)存,這是為什么呢???
如下圖:
當(dāng)我們執(zhí)行useTrap函數(shù)時(shí),注意,是沒(méi)有結(jié)束此函數(shù),boy和girl指針其實(shí)是被兩個(gè)智能指針托管的,所以他們的引用計(jì)數(shù)是2
useTrap函數(shù)結(jié)束后,函數(shù)中定義的智能指針被清掉,boy和girl指針的引用計(jì)數(shù)減1,還剩下1,對(duì)象中的智能指針還是托管他們的,所以函數(shù)結(jié)束后沒(méi)有將boy和gilr指針釋放的原因就是于此。
所以在使用shared_ptr智能指針時(shí),要注意避免對(duì)象交叉使用智能指針的情況! 否則會(huì)導(dǎo)致內(nèi)存泄露!
當(dāng)然,這也是有辦法解決的,那就是使用weak_ptr弱指針。
針對(duì)上面的情況,還講一下另一種情況。如果是單方獲得管理對(duì)方的共享指針,那么這樣著是可以正常釋放掉的!
例如:
void useTrap() { shared_ptr<Boy> spBoy(new Boy()); shared_ptr<Girl> spGirl(new Girl()); // 單方獲得管理 //spBoy->setGirlFriend(spGirl); spGirl->setBoyFriend(spBoy); }
反過(guò)來(lái)也是一樣的!
這是什么原理呢?
- 首先釋放spBoy,但是因?yàn)間irl對(duì)象里面的智能指針還托管著boy,boy的引用計(jì)數(shù)為2,所以釋放spBoy時(shí),引用計(jì)數(shù)減1,boy的引用計(jì)數(shù)為1;
- 在釋放spGirl,girl的引用計(jì)數(shù)減1,為零,開(kāi)始釋放girl的內(nèi)存,因?yàn)間irl里面還包含有托管boy的智能指針對(duì)象,所以也會(huì)進(jìn)行boyFriend的內(nèi)存釋放,boy的引用計(jì)數(shù)減1,為零,接著開(kāi)始釋放boy的內(nèi)存。最終所有的內(nèi)存都釋放了。
五、weak_ptr
weak_ptr 設(shè)計(jì)的目的是為配合 shared_ptr 而引入的一種智能指針來(lái)協(xié)助 shared_ptr 工作, 它只可以從一個(gè) shared_ptr 或另一個(gè) weak_ptr 對(duì)象構(gòu)造, 它的構(gòu)造和析構(gòu)不會(huì)引起引用記數(shù)的增加或減少。 同時(shí)weak_ptr 沒(méi)有重載*和->但可以使用 lock 獲得一個(gè)可用的 shared_ptr 對(duì)象。
- 弱指針的使用;
weak_ptr wpGirl_1; // 定義空的弱指針
weak_ptr wpGirl_2(spGirl); // 使用共享指針構(gòu)造
wpGirl_1 = spGirl; // 允許共享指針賦值給弱指針
- 弱指針也可以獲得引用計(jì)數(shù);
wpGirl_1.use_count()
- 弱指針不支持 * 和 -> 對(duì)指針的訪問(wèn);
在必要的使用可以轉(zhuǎn)換成共享指針 lock();
shared_ptr<Girl> sp_girl; sp_girl = wpGirl_1.lock(); // 使用完之后,再將共享指針置NULL即可 sp_girl = NULL;
使用代碼:
shared_ptr<Boy> spBoy(new Boy()); shared_ptr<Girl> spGirl(new Girl()); // 弱指針的使用 weak_ptr<Girl> wpGirl_1; // 定義空的弱指針 weak_ptr<Girl> wpGirl_2(spGirl); // 使用共享指針構(gòu)造 wpGirl_1 = spGirl; // 允許共享指針賦值給弱指針 cout << "spGirl \t use_count = " << spGirl.use_count() << endl; cout << "wpGirl_1 \t use_count = " << wpGirl_1.use_count() << endl; // 弱指針不支持 * 和 -> 對(duì)指針的訪問(wèn) /*wpGirl_1->setBoyFriend(spBoy); (*wpGirl_1).setBoyFriend(spBoy);*/ // 在必要的使用可以轉(zhuǎn)換成共享指針 shared_ptr<Girl> sp_girl; sp_girl = wpGirl_1.lock(); cout << sp_girl.use_count() << endl; // 使用完之后,再將共享指針置NULL即可 sp_girl = NULL;
當(dāng)然這只是一些使用上的小例子,具體用法如下:
請(qǐng)看Boy類(lèi)
#include <iostream> #include <string> #include <memory> using namespace std; class Girl; class Boy { public: Boy() { cout << "Boy 構(gòu)造函數(shù)" << endl; } ~Boy() { cout << "~Boy 析構(gòu)函數(shù)" << endl; } void setGirlFriend(shared_ptr<Girl> _girlFriend) { this->girlFriend = _girlFriend; // 在必要的使用可以轉(zhuǎn)換成共享指針 shared_ptr<Girl> sp_girl; sp_girl = this->girlFriend.lock(); cout << sp_girl.use_count() << endl; // 使用完之后,再將共享指針置NULL即可 sp_girl = NULL; } private: weak_ptr<Girl> girlFriend; }; class Girl { public: Girl() { cout << "Girl 構(gòu)造函數(shù)" << endl; } ~Girl() { cout << "~Girl 析構(gòu)函數(shù)" << endl; } void setBoyFriend(shared_ptr<Boy> _boyFriend) { this->boyFriend = _boyFriend; } private: shared_ptr<Boy> boyFriend; }; void useTrap() { shared_ptr<Boy> spBoy(new Boy()); shared_ptr<Girl> spGirl(new Girl()); spBoy->setGirlFriend(spGirl); spGirl->setBoyFriend(spBoy); } int main(void) { useTrap(); system("pause"); return 0; }
在類(lèi)中使用弱指針接管共享指針,在需要使用時(shí)就轉(zhuǎn)換成共享指針去使用即可!
自此問(wèn)題完美解決!
六、智能指針的使用陷阱
不要把一個(gè)原生指針給多個(gè)智能指針管理;
int *x = new int(10);
unique_ptr< int > up1(x);
unique_ptr< int > up2(x);
// 警告! 以上代碼使up1 up2指向同一個(gè)內(nèi)存,非常危險(xiǎn)
或以下形式:
up1.reset(x);
up2.reset(x);
記得使用u.release()的返回值;
在調(diào)用u.release()時(shí)是不會(huì)釋放u所指的內(nèi)存的,這時(shí)返回值就是對(duì)這塊內(nèi)存的唯一索引,如果沒(méi)有使用這個(gè)返回值釋放內(nèi)存或是保存起來(lái),這塊內(nèi)存就泄漏了.
禁止delete 智能指針get 函數(shù)返回的指針;
如果我們主動(dòng)釋放掉get 函數(shù)獲得的指針,那么智能 指針內(nèi)部的指針就變成野指針了,析構(gòu)時(shí)造成重復(fù)釋放,帶來(lái)嚴(yán)重后果!
禁止用任何類(lèi)型智能指針get 函數(shù)返回的指針去初始化另外一個(gè)智能指針!
shared_ptr< int > sp1(new int(10));
// 一個(gè)典型的錯(cuò)誤用法 shared_ptr< int > sp4(sp1.get());
七、總結(jié)
智能指針雖然使用起來(lái)很方便,但是要注意使用智能指針的一些陷阱,否則會(huì)造成嚴(yán)重的內(nèi)存報(bào)錯(cuò)或者內(nèi)存泄露等問(wèn)題!
到此這篇關(guān)于一文掌握C++ 智能指針全部用法的文章就介紹到這了,更多相關(guān)c++智能指針用法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vs2019創(chuàng)建WebService服務(wù)的實(shí)現(xiàn)
這篇文章主要介紹了vs2019創(chuàng)建WebService服務(wù)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03C++使用標(biāo)準(zhǔn)庫(kù)實(shí)現(xiàn)事件和委托以及信號(hào)和槽機(jī)制
這篇文章主要為大家詳細(xì)介紹了C++如何使用標(biāo)準(zhǔn)庫(kù)實(shí)現(xiàn)事件和委托以及信號(hào)和槽機(jī)制,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以參考一下2022-11-11c++ builder TreeView控件節(jié)點(diǎn)遍歷代碼
這篇文章介紹了c++ builder TreeView控件節(jié)點(diǎn)遍歷代碼,有需要的朋友可以參考一下2013-09-09C語(yǔ)言示例講解while循環(huán)語(yǔ)句的用法
在不少實(shí)際問(wèn)題中有許多具有規(guī)律性的重復(fù)操作,因此在程序中就需要重復(fù)執(zhí)行某些語(yǔ)句。一組被重復(fù)執(zhí)行的語(yǔ)句稱之為循環(huán)體,C語(yǔ)言while語(yǔ)句可以是單個(gè)語(yǔ)句,也可以是一個(gè)語(yǔ)句塊,其條件可以是任意表達(dá)式,true是任意非零值,當(dāng)條件為真時(shí),循環(huán)進(jìn)行迭代2022-06-06