溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C++中怎么使用智能指針

發(fā)布時間:2021-08-04 13:58:29 來源:億速云 閱讀:178 作者:Leah 欄目:開發(fā)技術(shù)

這篇文章給大家介紹C++中怎么使用智能指針,內(nèi)容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

一、為什么要使用智能指針

一句話帶過:智能指針就是幫我們C++程序員管理動態(tài)分配的內(nèi)存的,它會幫助我們自動釋放new出來的內(nèi)存,從而避免內(nèi)存泄漏!

如下例子就是內(nèi)存泄露的例子:

#include <iostream>
#include <string>
#include <memory>

using namespace std;


// 動態(tài)分配內(nèi)存,沒有釋放就return
void memoryLeak1() {
	string *str = new string("動態(tài)分配內(nèi)存!");
	return;
}

// 動態(tài)分配內(nèi)存,雖然有些釋放內(nèi)存的代碼,但是被半路截胡return了
int memoryLeak2() {
	string *str = new string("內(nèi)存泄露!");

	// ...此處省略一萬行代碼

	// 發(fā)生某些異常,需要結(jié)束函數(shù)
	if (1) {
		return -1;
	}

	delete str;	// 雖然寫了釋放內(nèi)存的代碼,但是遭到函數(shù)中段返回,使得指針沒有得到釋放
	return 1;
}


int main(void) {

	memoryLeak1();

	memoryLeak2();

	return 0;
}

memoryLeak1函數(shù)中,new了一個字符串指針,但是沒有delete就已經(jīng)return結(jié)束函數(shù)了,導(dǎo)致內(nèi)存沒有被釋放,內(nèi)存泄露!
memoryLeak2函數(shù)中,new了一個字符串指針,雖然在函數(shù)末尾有些釋放內(nèi)存的代碼delete str,但是在delete之前就已經(jīng)return了,所以內(nèi)存也沒有被釋放,內(nèi)存泄露!

使用指針,我們沒有釋放,就會造成內(nèi)存泄露。但是我們使用普通對象卻不會!

思考:如果我們分配的動態(tài)內(nèi)存都交由有生命周期的對象來處理,那么在對象過期時,讓它的析構(gòu)函數(shù)刪除指向的內(nèi)存,這看似是一個 very nice 的方案?

智能指針就是通過這個原理來解決指針自動釋放的問題!

  1. C++98 提供了 auto_ptr 模板的解決方案

  2. C++11 增加unique_ptr、shared_ptr 和weak_ptr


二、auto_ptr

auto_ptr 是c++ 98定義的智能指針模板,其定義了管理指針的對象,可以將new 獲得(直接或間接)的地址賦給這種對象。當(dāng)對象過期時,其析構(gòu)函數(shù)將使用delete 來釋放內(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òu)造函數(shù)和析構(gòu)函數(shù)都輸出一個字符串用作提示!
定義一個私有成員變量,賦值20.
再定義一個私有成員方法用于返回這個私有成員變量。

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這個類的對象,卻沒有釋放時。。。

int main(void) {
	Test *test = new Test;

	return 0;
}

C++中怎么使用智能指針

可以看到,只是打印了構(gòu)造函數(shù)這個字符串,而析構(gòu)函數(shù)的字符卻沒有被打印,說明并沒有調(diào)用析構(gòu)函數(shù)!這就導(dǎo)致了內(nèi)存泄露!
解決內(nèi)存泄露的辦法,要么手動delete,要么使用智能指針!

使用智能指針:

// 定義智能指針
auto_ptr<Test> test(new Test);

智能指針可以像普通指針那樣使用:

cout << "test->debug:" << test->getDebug() << endl;
cout << "(*test).debug:" << (*test).getDebug() << endl;

這時再試試:

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;
}

C++中怎么使用智能指針

自動調(diào)用了析構(gòu)函數(shù)。
為什么智能指針可以像普通指針那樣使用???
因為其里面重載了 * 和 -> 運算符, * 返回普通對象,而 -> 返回指針對象。

C++中怎么使用智能指針

具體原因不用深究,只需知道他為什么可以這樣操作就像!
函數(shù)中返回的是調(diào)用get()方法返回的值,那么這個get()是什么呢?

智能指針的三個常用函數(shù):

get() 獲取智能指針托管的指針地址

// 定義智能指針
auto_ptr<Test> test(new Test);

Test *tmp = test.get();		// 獲取指針返回
cout << "tmp->debug:" << tmp->getDebug() << endl;

但我們一般不會這樣使用,因為都可以直接使用智能指針去操作,除非有一些特殊情況。
函數(shù)原型:

_NODISCARD _Ty * get() const noexcept
{	// return wrapped pointer
	return (_Myptr);
}

release() 取消智能指針對動態(tài)內(nèi)存的托管

// 定義智能指針
auto_ptr<Test> test(new Test);

Test *tmp2 = test.release();	// 取消智能指針對動態(tài)內(nèi)存的托管
delete tmp2;	// 之前分配的內(nèi)存需要自己手動釋放

也就是智能指針不再對該指針進行管理,改由管理員進行管理!
函數(shù)原型:

_Ty * release() noexcept
{	// return wrapped pointer and give up ownership
	_Ty * _Tmp = _Myptr;
	_Myptr = nullptr;
	return (_Tmp);
}

reset() 重置智能指針托管的內(nèi)存地址,如果地址不一致,原來的會被析構(gòu)掉

// 定義智能指針
auto_ptr<Test> test(new Test);

test.reset();			// 釋放掉智能指針托管的指針內(nèi)存,并將其置NULL

test.reset(new Test());	// 釋放掉智能指針托管的指針內(nèi)存,并將參數(shù)指針取代之

reset函數(shù)會將參數(shù)的指針(不指定則為NULL),與托管的指針比較,如果地址不一致,那么就會析構(gòu)掉原來托管的指針,然后使用參數(shù)的指針替代之。然后智能指針就會托管參數(shù)的那個指針了。
函數(shù)原型:

void reset(_Ty * _Ptr = nullptr)
{	// destroy designated object and store new pointer
	if (_Ptr != _Myptr)
		delete _Myptr;
	_Myptr = _Ptr;
}

使用建議:

盡可能不要將auto_ptr 變量定義為全局變量或指針;

// 沒有意義,全局變量也是一樣
auto_ptr<Test> *tp = new auto_ptr<Test>(new Test);	

除非自己知道后果,不要把auto_ptr 智能指針賦值給同類型的另外一個 智能指針;

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ù)制或者賦值都會改變資源的所有權(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會先將自己原先托管的指針釋放掉,然后接收托管p2所托管的指針,
// 然后p2所托管的指針制NULL,也就是p1托管了p2托管的指針,而p2放棄了托管。
p1 = p2;	
cout << "p1 = p2 賦值后:" << endl;
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;

C++中怎么使用智能指針

2). 在STL容器中使用auto_ptr存在著重大風(fēng)險,因為容器內(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修飾成右值,才可以進行插入容器中
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)險來了:
vec[0] = vec[1];	// 如果進行賦值,問題又回到了上面一個問題中。
cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;

訪問越界了!

C++中怎么使用智能指針

3). 不支持對象數(shù)組的內(nèi)存管理

auto_ptr<int[]> array(new int[5]);	// 不能這樣定義

C++中怎么使用智能指針

所以,C++11用更嚴(yán)謹(jǐn)?shù)膗nique_ptr 取代了auto_ptr!

測試代碼:

#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;
};

// 不要定義為全局變量,沒有意義
//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();		// 獲取指針返回
	cout << "tmp->debug:" << tmp->getDebug() << endl;


	// release方法
	Test *tmp2 = test.release();	// 取消智能指針對動態(tài)內(nèi)存的托管
	delete tmp2;	// 之前分配的內(nèi)存需要自己手動釋放


	// reset方法:重置智能指針托管的內(nèi)存地址,如果地址不一致,原來的會被析構(gòu)掉
	test.reset();			// 釋放掉智能指針托管的指針內(nèi)存,并將其置NULL
	test.reset(new Test());	// 釋放掉智能指針托管的指針內(nèi)存,并將參數(shù)指針取代之


	// 忠告:不要將智能指針定義為指針
	//auto_ptr<Test> *tp = new auto_ptr<Test>(new Test);

	// 忠告:不要定義指向智能指針對象的指針變量
	//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);

	// ...此處省略一萬行代碼

	// 發(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)險,因為容器內(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)險來了:
	vec[0] = vec[1];
	cout << "vec.at(0):" << *vec.at(0) << endl;
	cout << "vec[1]:" << *vec[1] << endl;


	// 弊端3.不支持對象數(shù)組的內(nèi)存管理
	//auto_ptr<int[]> array(new int[5]);	// 不能這樣定義
	return 0;
}

三、unique_ptr

auto_ptr是用于C++11之前的智能指針。由于 auto_ptr 基于排他所有權(quán)模式:兩個指針不能指向同一個資源,復(fù)制或賦值都會改變資源的所有權(quán)。auto_ptr 主要有三大問題:

  • 復(fù)制和賦值會改變資源的所有權(quán),不符合人的直覺。

  • 在 STL 容器中使用auto_ptr存在重大風(fēng)險,因為容器內(nèi)的元素必需支持可復(fù)制(copy constructable)和可賦值(assignable)。

  • 不支持對象數(shù)組的操作

以上問題已經(jīng)在上面體現(xiàn)出來了,下面將使用unique_ptr解決這些問題。

所以,C++11用更嚴(yán)謹(jǐn)?shù)膗nique_ptr 取代了auto_ptr!

unique_ptr 和 auto_ptr用法幾乎一樣,除了一些特殊。

unique_ptr特性

  1. 基于排他所有權(quán)模式:兩個指針不能指向同一個資源

  2. 無法進行左值unique_ptr復(fù)制構(gòu)造,也無法進行左值復(fù)制賦值操作,但允許臨時右值賦值構(gòu)造和賦值

  3. 保存指向某個對象的指針,當(dāng)它本身離開作用域時會自動釋放它指向的對象。

  4. 在容器中保存指針是安全的

A. 無法進行左值復(fù)制賦值操作,但允許臨時右值賦值構(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;

C++中怎么使用智能指針

運行截圖:

C++中怎么使用智能指針

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;

C++中怎么使用智能指針

當(dāng)然,運行后是直接報錯的,因為vec[1]已經(jīng)是NULL了,再繼續(xù)訪問就越界了。

C. 支持對象數(shù)組的內(nèi)存管理

// 會自動調(diào)用delete [] 函數(shù)去釋放內(nèi)存
unique_ptr<int[]> array(new int[5]);	// 支持這樣定義

除了上面ABC三項外,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; }
};


// 自定義一個內(nèi)存釋放其
class DestructTest {
	public:
	void operator()(Test *pt) {
		pt->doSomething();
		delete pt;
	}
};

// unique_ptr<T> up; 空的unique_ptr,可以指向類型為T的對象
unique_ptr<Test> t1;

// unique_ptr<T> up1(new T());	定義unique_ptr,同時指向類型為T的對象
unique_ptr<Test> t2(new Test);

// unique_ptr<T[]> up;	空的unique_ptr,可以指向類型為T[的數(shù)組對象
unique_ptr<int[]> t3;

// unique_ptr<T[]> up1(new T[]);	定義unique_ptr,同時指向類型為T的數(shù)組對象
unique_ptr<int[]> t4(new int[5]);

// unique_ptr<T, D> up();	空的unique_ptr,接受一個D類型的刪除器D,使用D釋放內(nèi)存
unique_ptr<Test, DestructTest> t5;

// unique_ptr<T, D> up(new T());	定義unique_ptr,同時指向類型為T的對象,接受一個D類型的刪除器D,使用刪除器D來釋放內(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);	// 必須使用移動語義,結(jié)果,t7的內(nèi)存釋放,t8的內(nèi)存交給t7管理
t7->doSomething();

主動釋放對象

unique_ptr<Test> t9(new Test);
t9 = NULL;
t9 = nullptr;
t9.reset();

放棄對象的控制權(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指針時,會先取消p1的托管,然后再對str的托管
}

// 此時p1已經(jīng)沒有托管內(nèi)容指針了,為NULL,在使用它就會內(nèi)存報錯!
cout << "str:" << *p1 << endl;

C++中怎么使用智能指針

這是由于auto_ptr 與 unique_ptr的排他性所導(dǎo)致的!
為了解決這樣的問題,我們可以使用shared_ptr指針指針!


四、shared_ptr

熟悉了unique_ptr 后,其實我們發(fā)現(xiàn)unique_ptr 這種排他型的內(nèi)存管理并不能適應(yīng)所有情況,有很大的局限!如果需要多個指針變量共享怎么辦?

如果有一種方式,可以記錄引用特定內(nèi)存對象的智能指針數(shù)量,當(dāng)復(fù)制或拷貝時,引用計數(shù)加1,當(dāng)智能指針析構(gòu)時,引用計數(shù)減1,如果計數(shù)為零,代表已經(jīng)沒有指針指向這塊內(nèi)存,那么我們就釋放它!這就是 shared_ptr 采用的策略!

C++中怎么使用智能指針

例:

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;
	}
};

引用計數(shù)的使用

調(diào)用use_count函數(shù)可以獲得當(dāng)前托管指針的引用計數(shù)。

shared_ptr<Person> sp1;

shared_ptr<Person> sp2(new Person(2));

// 獲取智能指針管控的共享指針的數(shù)量	use_count():引用計數(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);就是在使用引用計數(shù)了。

sp1 = sp2; --> sp1和sp2共同托管同一個指針,所以他們的引用計數(shù)為2;
shared_ptr< Person > sp3(sp1); --> sp1和sp2和sp3共同托管同一個指針,所以他們的引用計數(shù)為3;

C++中怎么使用智能指針

構(gòu)造

1). shared_ptr< T > sp1; 空的shared_ptr,可以指向類型為T的對象

shared_ptr<Person> sp1;
Person *person1 = new Person(1);
sp1.reset(person1);	// 托管person1

2). shared_ptr< T > sp2(new T()); 定義shared_ptr,同時指向類型為T的對象

shared_ptr<Person> sp2(new Person(2));
shared_ptr<Person> sp3(sp1);

3). shared_ptr<T[]> sp4; 空的shared_ptr,可以指向類型為T[]的數(shù)組對象 C++17后支持

shared_ptr<Person[]> sp4;

4). shared_ptr<T[]> sp5(new T[] { … }); 指向類型為T的數(shù)組對象 C++17后支持

shared_ptr<Person[]> sp5(new Person[5] { 3, 4, 5, 6, 7 });

5). shared_ptr< T > sp6(NULL, D()); //空的shared_ptr,接受一個D類型的刪除器,使用D釋放內(nèi)存

shared_ptr<Person> sp6(NULL, DestructPerson());

6). shared_ptr< T > sp7(new T(), D()); //定義shared_ptr,指向類型為T的對象,接受一個D類型的刪除器,使用D刪除器來釋放內(nèi)存

shared_ptr<Person> sp7(new Person(8), DestructPerson());

初始化

1). 方式一:構(gòu)造函數(shù)

shared_ptr<int> up1(new int(10));  // int(10) 的引用計數(shù)為1
shared_ptr<int> up2(up1);  // 使用智能指針up1構(gòu)造up2, 此時int(10) 引用計數(shù)為2

2). 方式二:使用make_shared 初始化對象,分配內(nèi)存效率更高(推薦使用)
make_shared函數(shù)的主要功能是在動態(tài)內(nèi)存中分配一個對象并初始化它,返回指向此對象的shared_ptr; 用法:
make_shared<類型>(構(gòu)造類型對象需要的參數(shù)列表);

shared_ptr<int> up3 = make_shared<int>(2); // 多個參數(shù)以逗號','隔開,最多接受十個
shared_ptr<string> up4 = make_shared<string>("字符串");
shared_ptr<Person> up5 = make_shared<Person>(9);

賦值

shared_ptrr<int> up1(new int(10));  // int(10) 的引用計數(shù)為1
shared_ptr<int> up2(new int(11));   // int(11) 的引用計數(shù)為1
up1 = up2;	// int(10) 的引用計數(shù)減1,計數(shù)歸零內(nèi)存釋放,up2共享int(11)給up1, int(11)的引用計數(shù)為2

主動釋放對象

shared_ptrr<int> up1(new int(10));
up1 = nullptr ;	// int(10) 的引用計數(shù)減1,計數(shù)歸零內(nèi)存釋放 
// 或
up1 = NULL; // 作用同上

重置
p.reset() ; 將p重置為空指針,所管理對象引用計數(shù) 減1
p.reset(p1); 將p重置為p1(的值),p 管控的對象計數(shù)減1,p接管對p1指針的管控
p.reset(p1,d); 將p重置為p1(的值),p 管控的對象計數(shù)減1并使用d作為刪除器
p1是一個指針!

交換
p1 和 p2 是智能指針

std::swap(p1,p2); // 交換p1 和p2 管理的對象,原對象的引用計數(shù)不變
p1.swap(p2);    // 交換p1 和p2 管理的對象,原對象的引用計數(shù)不變

shared_ptr使用陷阱

shared_ptr作為被管控的對象的成員時,小心因循環(huán)引用造成無法釋放資源!

如下代碼:
Boy類中有Girl的智能指針;
Girl類中有Boy的智能指針;
當(dāng)他們交叉互相持有對方的管理對象時…

#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);
	// 此時boy和girl的引用計數(shù)都是2
}


int main(void) {
	useTrap();

	system("pause");
	return 0;
}

運行截圖:

C++中怎么使用智能指針

可以看出,程序結(jié)束了,但是并沒有釋放內(nèi)存,這是為什么呢???

如下圖:
當(dāng)我們執(zhí)行useTrap函數(shù)時,注意,是沒有結(jié)束此函數(shù),boy和girl指針其實是被兩個智能指針托管的,所以他們的引用計數(shù)是2

C++中怎么使用智能指針

useTrap函數(shù)結(jié)束后,函數(shù)中定義的智能指針被清掉,boy和girl指針的引用計數(shù)減1,還剩下1,對象中的智能指針還是托管他們的,所以函數(shù)結(jié)束后沒有將boy和gilr指針釋放的原因就是于此。

C++中怎么使用智能指針

所以在使用shared_ptr智能指針時,要注意避免對象交叉使用智能指針的情況! 否則會導(dǎo)致內(nèi)存泄露!

當(dāng)然,這也是有辦法解決的,那就是使用weak_ptr弱指針。

針對上面的情況,還講一下另一種情況。如果是單方獲得管理對方的共享指針,那么這樣著是可以正常釋放掉的!
例如:

void useTrap() {
	shared_ptr<Boy> spBoy(new Boy());
	shared_ptr<Girl> spGirl(new Girl());

	// 單方獲得管理
	//spBoy->setGirlFriend(spGirl);
	spGirl->setBoyFriend(spBoy);	
}

C++中怎么使用智能指針

反過來也是一樣的!

這是什么原理呢?

  • 首先釋放spBoy,但是因為girl對象里面的智能指針還托管著boy,boy的引用計數(shù)為2,所以釋放spBoy時,引用計數(shù)減1,boy的引用計數(shù)為1;

  • 在釋放spGirl,girl的引用計數(shù)減1,為零,開始釋放girl的內(nèi)存,因為girl里面還包含有托管boy的智能指針對象,所以也會進行boyFriend的內(nèi)存釋放,boy的引用計數(shù)減1,為零,接著開始釋放boy的內(nèi)存。最終所有的內(nèi)存都釋放了。


五、weak_ptr

weak_ptr 設(shè)計的目的是為配合 shared_ptr 而引入的一種智能指針來協(xié)助 shared_ptr 工作, 它只可以從一個 shared_ptr 或另一個 weak_ptr 對象構(gòu)造, 它的構(gòu)造和析構(gòu)不會引起引用記數(shù)的增加或減少。 同時weak_ptr 沒有重載*和->但可以使用 lock 獲得一個可用的 shared_ptr 對象。

  1. 弱指針的使用;

weak_ptr wpGirl_1; // 定義空的弱指針
weak_ptr wpGirl_2(spGirl); // 使用共享指針構(gòu)造
wpGirl_1 = spGirl; // 允許共享指針賦值給弱指針

  1. 弱指針也可以獲得引用計數(shù);

wpGirl_1.use_count()

  1. 弱指針不支持 * 和 -> 對指針的訪問;

C++中怎么使用智能指針

在必要的使用可以轉(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;

	
// 弱指針不支持 * 和 -> 對指針的訪問
/*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)然這只是一些使用上的小例子,具體用法如下:

請看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;
}

C++中怎么使用智能指針

在類中使用弱指針接管共享指針,在需要使用時就轉(zhuǎn)換成共享指針去使用即可!

自此問題完美解決!


六、智能指針的使用陷阱

不要把一個原生指針給多個智能指針管理;

int *x = new int(10);
unique_ptr< int > up1(x);
unique_ptr< int > up2(x);
// 警告! 以上代碼使up1 up2指向同一個內(nèi)存,非常危險
或以下形式:
up1.reset(x);
up2.reset(x);

記得使用u.release()的返回值;
在調(diào)用u.release()時是不會釋放u所指的內(nèi)存的,這時返回值就是對這塊內(nèi)存的唯一索引,如果沒有使用這個返回值釋放內(nèi)存或是保存起來,這塊內(nèi)存就泄漏了.

禁止delete 智能指針get 函數(shù)返回的指針;
如果我們主動釋放掉get 函數(shù)獲得的指針,那么智能 指針內(nèi)部的指針就變成野指針了,析構(gòu)時造成重復(fù)釋放,帶來嚴(yán)重后果!

禁止用任何類型智能指針get 函數(shù)返回的指針去初始化另外一個智能指針!
shared_ptr< int > sp1(new int(10));
// 一個典型的錯誤用法 shared_ptr< int > sp4(sp1.get());

關(guān)于C++中怎么使用智能指針就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

c++
AI