一文掌握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; // 雖然寫了釋放內(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<類型> 變量名(new 類型)
例 如:
auto_ptr< string > str(new string(“我要成為大牛~ 變得很牛逼!”));
auto_ptr<vector< int >> av(new vector< int >());
auto_ptr< int > array(new int[10]);
例:
我們先定義一個(gè)類,類的構(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è)類的對(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 智能指針賦值給同類型的另外一個(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,可以指向類型為T的對(duì)象
unique_ptr<Test> t1;
// unique_ptr<T> up1(new T()); 定義unique_ptr,同時(shí)指向類型為T的對(duì)象
unique_ptr<Test> t2(new Test);
// unique_ptr<T[]> up; 空的unique_ptr,可以指向類型為T[的數(shù)組對(duì)象
unique_ptr<int[]> t3;
// unique_ptr<T[]> up1(new T[]); 定義unique_ptr,同時(shí)指向類型為T的數(shù)組對(duì)象
unique_ptr<int[]> t4(new int[5]);
// unique_ptr<T, D> up(); 空的unique_ptr,接受一個(gè)D類型的刪除器D,使用D釋放內(nèi)存
unique_ptr<Test, DestructTest> t5;
// unique_ptr<T, D> up(new T()); 定義unique_ptr,同時(shí)指向類型為T的對(duì)象,接受一個(gè)D類型的刪除器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,可以指向類型為T的對(duì)象
shared_ptr<Person> sp1; Person *person1 = new Person(1); sp1.reset(person1); // 托管person1
2). shared_ptr< T > sp2(new T()); 定義shared_ptr,同時(shí)指向類型為T的對(duì)象
shared_ptr<Person> sp2(new Person(2)); shared_ptr<Person> sp3(sp1);
3). shared_ptr<T[]> sp4; 空的shared_ptr,可以指向類型為T[]的數(shù)組對(duì)象 C++17后支持
shared_ptr<Person[]> sp4;
4). shared_ptr<T[]> sp5(new T[] { … }); 指向類型為T的數(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類型的刪除器,使用D釋放內(nèi)存
shared_ptr<Person> sp6(NULL, DestructPerson());
6). shared_ptr< T > sp7(new T(), D()); //定義shared_ptr,指向類型為T的對(duì)象,接受一個(gè)D類型的刪除器,使用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<類型>(構(gòu)造類型對(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類中有Girl的智能指針;
Girl類中有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類
#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;
}

在類中使用弱指針接管共享指針,在需要使用時(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)重后果!
禁止用任何類型智能指針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)文章
C/C++?判斷計(jì)算機(jī)存儲(chǔ)器字節(jié)序(端序)的幾種方式
字節(jié)序是計(jì)算機(jī)存儲(chǔ)數(shù)據(jù)的格式,主存儲(chǔ)器(主存)的字節(jié)序?qū)Τ绦虻囊浦残院图嫒菪灾陵P(guān)重要,利用聯(lián)合體、指針、位移和掩碼等方法可以檢測(cè)和處理字節(jié)序問(wèn)題,對(duì)于內(nèi)存數(shù)據(jù)操作重要,也關(guān)系到跨平臺(tái)和網(wǎng)絡(luò)通信的數(shù)據(jù)處理2024-10-10
C++實(shí)現(xiàn)LeetCode(88.混合插入有序數(shù)組)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(88.混合插入有序數(shù)組),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
基于C++實(shí)現(xiàn)簡(jiǎn)單日期計(jì)算器
這篇文章主要介紹了基于C++實(shí)現(xiàn)簡(jiǎn)單日期計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05
OpenCV reshape函數(shù)實(shí)現(xiàn)矩陣元素序列化
reshape函數(shù)是OpenCV中一個(gè)很有用的函數(shù),不僅可以改變矩陣的通道數(shù),還可以對(duì)矩陣元素進(jìn)行序列化。本文將主要介紹如何通過(guò)reshape實(shí)現(xiàn)矩陣元素序列化,需要的小伙伴可以參考一下2021-12-12
項(xiàng)目之C++如何實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接池
這篇文章主要介紹了項(xiàng)目之C++如何實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接池問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單三子棋游戲
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單三子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-09-09

