【C++】智能指針(auto_ptr,shared_ptr,unique_ptr)及 shared_ptr 強引用原理

傳統指針存在的問題

傳統指針存在的問題

  • 需要手動管理內存
  • 容易發生內存泄露(忘記釋放、出現異常等)
  • 釋放之後產生野指針

智能指針就是爲了解決傳統指針存在的問題

  • auto_ptr:屬於 C++98 標準,在 C++11 中已經不推薦使用
    (有缺陷,比如不能用於數組)
  • shared_ptr:屬於 C++11 標準
  • unique_ptr:屬於 C++11 標準

我們來看一段很簡單的代碼,體會下傳統指針存在的問題。

#include<iostream>
using namespace std;

class Person {
	int m_age;
public:
	Person() {
		cout << "Person()" << endl;
	}
	Person(int age) : m_age(age) {
		cout << "Person(int)" << endl;
	}
	~Person() {
		cout << "~Person()" << endl;
	}
	void run() {
		cout << "run() - " << m_age << endl;
	}
};

int main() {

	cout << 1 << endl;
	{
		Person *p = new Person(20);
		p->run();
		// delete p; // 手動釋放內存
	}
	cout << 2 << endl;
	
	return 0;
}
1
Person(int)
run() - 20
2

可以看出,如果我們不手動釋放內存(delete p;),傳統指針不會自動釋放內存(沒有調用析構函數)。

當我們手動釋放內存以後,結果會變成下面這樣(調用了析構函數)。

1
Person(int)
run() - 20
~Person()
2

然後我們使用智能指針試一下。

auto_ptr

auto_ptr 屬於 C++98 標準,現在不推薦使用這個了。但是每個智能指針在用法上沒有太大區別。

這段代碼與上面幾乎完全一樣(沒有delete),但是使用了智能指針 auto_ptr。

#include<iostream>
using namespace std;

class Person {
	int m_age;
public:
	Person() {
		cout << "Person()" << endl;
	}
	Person(int age) : m_age(age) {
		cout << "Person(int)" << endl;
	}
	~Person() {
		cout << "~Person()" << endl;
	}
	void run() {
		cout << "run() - " << m_age << endl;
	}
};

int main() {

	// 可以理解爲:智能指針p(棧空間) 指向了 Person對象(堆空間)
	cout << 1 << endl;
	{
		auto_ptr<Person> p(new Person(20));
		p->run();
		// 智能指針不需要手動 delete
	}
	cout << 2 << endl;
	
	return 0;
}
1
Person(int)
run() - 20
~Person()
2

智能指針會自動釋放內存。(有侷限性,後面會寫到)

智能指針的簡單自實現

其實智能指針的原理很簡單,我們可以自己實現一個很簡單的智能指針。

思路其實很簡單,就是寫一個智能指針類,將指針變爲這個類的屬性,在類的析構函數中釋放內存即可。如果想通過 -> 調用成員函數,只需要重載運算符 -> 即可。

#include<iostream>
using namespace std;

class Person {
	int m_age;
public:
	Person() {
		cout << "Person()" << endl;
	}
	Person(int age) : m_age(age) {
		cout << "Person(int)" << endl;
	}
	~Person() {
		cout << "~Person()" << endl;
	}
	void run() {
		cout << "run() - " << m_age << endl;
	}
};

// 智能指針的簡單自實現
template <typename T>
class SmartPoint {
	T *m_obj;
public:
	SmartPoint(T *obj) : m_obj(obj){}
	~SmartPoint() {
		if (m_obj == nullptr) return;
		delete m_obj;
	}
	T* operator->() { // 運算符重載
		return m_obj;
	}
};


int main() {

	cout << 1 << endl;
	{
		SmartPoint<Person> p(new Person(20));
		p->run();
	}
	cout << 2 << endl;

	return 0;
}
1
Person(int)
run() - 20
~Person()
2

可以看到我們實現的簡單智能指針達到了我們的要求。

shared_ptr

shared_ptr 的設計理念:

  • 可以通過一個已存在的智能指針初始化一個新的智能指針
shared_ptr<Person> p1(new Person());
shared_ptr<Person> p2(p1);  // p2 指向 p1
shared_ptr<Person> p3 = p2; // p3 指向 p2,實際上就是指向 p1
Person()
~Person()
  • 針對數組的用法
shared_ptr<Person[]> p(new Person[5]); // 標準的寫法
// 下面這種寫法(結合Lambda)也可以,但是感覺沒必要
// shared_ptr<Person> p(new Person[5], [](Person *p) {delete[] p; });
Person()
Person()
Person()
Person()
Person()
~Person()
~Person()
~Person()
~Person()
~Person()
  • 多個 shared_ptr 可以指向同一個對象,當最後一個 shared_ptr 在作用域範圍內結束時,對象纔會被自動釋放
int main() {
	{
		shared_ptr<Person> p4;
		{
			shared_ptr<Person> p1(new Person(10)); 
			shared_ptr<Person> p3;
			{
				shared_ptr<Person> p2 = p1; // p2 指向 p1
				p3 = p2; // p3 指向 p2
				cout << 1 << endl;
			}
			p4 = p3; // p4 指向 p3
			cout << 2 << endl;
		}
		cout << 3 << endl;
	}

	return 0;
}
Person(int)
1
2
3
~Person()

可以看到,p1、p2、p3、p4 都指向同一個對象,p1、p2、p3 作用域範圍結束後都沒有銷燬對象,只有當 p4 作用域範圍後(最後一個 shared_ptr 作用域範圍
結束
)後對象被銷燬。

shared_ptr 內存銷燬的原理(強引用)

  • 一個 shared_ptr 會對一個對象產生 強引用(strong reference)
  • 每個對象都有個與之對應的強引用計數,記錄着當前對象被多少個 shared_ptr 強引用着
  • 可以通過 shared_ptr 的 use_count函數 獲得強引用計數
  • 當有一個新的 shared_ptr 指向對象時,對象的強引用計數就會+1
  • 當有一個 shared_ptr 銷燬時(比如作用域結束),對象的強引用計數就會-1
  • 當一個對象的強引用計數爲0時(沒有任何shared_ptr指向對象時),對象就會自動銷燬(析構)
int main() {
	{
		shared_ptr<Person> p4;
		{
			shared_ptr<Person> p1(new Person(10));
			cout << p1.use_count() << endl; // 1
	
			shared_ptr<Person> p3;
			{
				shared_ptr<Person> p2 = p1;
				cout << p2.use_count() << endl; // 2
	
				p3 = p2; // 3
			} // p2 銷燬,3-1=2
			cout << p3.use_count() << endl; // 2
	
			p4 = p3;
			cout << p4.use_count() << endl; // 3
		} // p3,p1 銷燬,3-2=1
		cout << p4.use_count() << endl; // 1
	}

	return 0;
}
Person(int)
1
2
2
3
1
~Person()

來看一段有問題的代碼。

int main() {
	Person *p = new Person(20);
	{
		shared_ptr<Person> p1(p);
	} // ~Persn(),釋放內存
	{
		shared_ptr<Person> p2(p);
	} // ~Person(),又釋放內存 
	
	// 總共會釋放兩次內存
}

shared_ptr 的循環引用

在這裏插入圖片描述
可以看到,這個例子中,堆空間裏的 Person對象 與 Car對象 互相使用着,導致雙方的 shared_ptr 強引用數量不會爲0,所以不會自動釋放內存。

循環引用的解決方案是使用 weak_ptr。

weak_ptr

  • weak_ptr 會對一個對象產生弱引用
  • weak_ptr 可以指向對象解決 shared_ptr 的循環引用問題

在這裏插入圖片描述
在這裏插入圖片描述

unique_ptr

  • unique_ptr 也會對一個對象產生強引用,它可以確保同一時間只有1個指針指向對象
  • 當 unique_ptr 銷燬時(作用域結束時),其指向的對象也就自動銷燬了
  • 可以使用 std::move函數 轉移 unique_ptr 的所有權
int main() {
	// ptr1 強引用着 Person 對象
	unique_ptr<Person> ptr1(new Person(20));
	// 轉移之後,ptr2 強引用着 Person 對象
	unique_ptr<Person > ptr2 = std::move(ptr1);
	
	// unique_ptr 確保同一時間只有1個指針指向對象
	// unique_ptr<Person> ptr3(ptr2); // 報錯
	// unique_ptr<Person> ptr3 = ptr2; // 報錯
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章