C++智能指針

爲什麼C++要引入智能指針?

 智能指針的引入是爲了解決程序員動態分配內存後的刪除十分麻煩的問題。簡單的說就是解決堆棧內存泄露。

智能指針的作用:

理解智能指針需要從下面三個層次:

  1. 從較淺的層面看,智能指針是利用了一種叫做RAII(資源獲取即初始化)的技術對普通的指針進行封裝,這使得智能指針實質是一個對象,行爲表現的卻像一個指針。
  2. 智能指針的作用是防止忘記調用delete釋放內存和程序異常的進入catch塊忘記釋放內存。另外指針的釋放時機也是非常有考究的,多次釋放同一個指針會造成程序崩潰,這些都可以通過智能指針來解決。
  3. 智能指針還有一個作用是把值語義轉換成引用語義。C++和Java有一處最大的區別在於語義不同,在Java裏面下列代碼:

  Animal a = new Animal();

  Animal b = a;

     你當然知道,這裏其實只生成了一個對象,a和b僅僅是把持對象的引用而已。但在C++中不是這樣,

     Animal a;

     Animal b = a;

     這裏卻是就是生成了兩個對象。

     關於值語言參考這篇文章http://www.cnblogs.com/Solstice/archive/2011/08/16/2141515.html

智能指針有四種:shared_ptr、unique_ptr、weak_ptr,auto_ptr(已被棄用),均放在<memory>頭文件中

 

shared_ptr:

shared_ptr多個指針指向相同的對象。shared_ptr使用引用計數,每一個shared_ptr的拷貝都指向相同的內存。每使用他一次,內部的引用計數加1,每析構一次,內部的引用計數減1,減爲0時,自動刪除所指向的堆內存。shared_ptr內部的引用計數是線程安全的,但是對象的讀取需要加鎖。

  • 初始化。智能指針是個模板類,可以指定類型,傳入指針通過構造函數初始化。也可以使用make_shared函數初始化。不能將指針直接賦值給一個智能指針,一個是類,一個是指針。例如std::shared_ptr<int> p4 = new int(1);的寫法是錯誤的
  • 拷貝和賦值。拷貝使得對象的引用計數增加1,賦值使得原對象引用計數減1,當計數爲0時,自動釋放內存。後來指向的對象引用計數加1,指向後來的對象。
  • get函數獲取原始指針
  • 注意不要用一個原始指針初始化多個shared_ptr,否則會造成二次釋放同一內存
  • 注意避免循環引用,shared_ptr的一個最大的陷阱是循環引用,循環,循環引用會導致堆內存無法正確釋放,導致內存泄漏。循環引用在weak_ptr中介紹。
int main() {
	int a = 10;
	shared_ptr<int> ptra = make_shared<int>(a);
	shared_ptr<int> ptra2(ptra); //調用拷貝構造函數
	cout << ptra.use_count() <<endl;

	int b = 20;
	int *pb = &a;
	shared_ptr<int> ptrb = make_shared<int>(b);
	ptra2 = ptrb; //賦值
	pb = ptrb.get(); //獲取原始指針

	cout << ptra.use_count() << endl;
	cout << ptrb.use_count() << endl;
}
//結果 2 1 2

unique_ptr:

unique_ptr“唯一”擁有其所指對象,同一時刻只能有一個unique_ptr指向給定對象(通過禁止拷貝語義、只有移動語義來實現)。相比與原始指針unique_ptr用於其RAII的特性,使得在出現異常的情況下,動態資源能得到釋放。unique_ptr指針本身的生命週期:從unique_ptr指針創建時開始,直到離開作用域。離開作用域時,若其指向對象,則將其所指對象銷燬(默認使用delete操作符,用戶可指定其他操作)。unique_ptr指針與其所指對象的關係:在智能指針生命週期內,可以改變智能指針所指對象,如創建智能指針時通過構造函數指定、通過reset方法重新指定、通過release方法釋放所有權、通過移動語義轉移所有權。

int main() {
	unique_ptr<int> uptr(new int(10));  //綁定動態對象
	//unique_ptr<int> uptr2 = uptr;  //不能賦值
	//unique_ptr<int> uptr2(uptr);  //不能拷貝
	unique_ptr<int> uptr2 = move(uptr); //轉換所有權
	uptr2.release(); //釋放所有權
}

weak_ptr:

weak_ptr是爲了配合shared_ptr而引入的一種智能指針,因爲它不具有普通指針的行爲,沒有重載operator*和->,它的最大作用在於協助shared_ptr工作,像旁觀者那樣觀測資源的使用情況。weak_ptr可以從一個shared_ptr或者另一個weak_ptr對象構造,獲得資源的觀測權。但weak_ptr沒有共享資源,它的構造不會引起指針引用計數的增加。使用weak_ptr的成員函數use_count()可以觀測資源的引用計數,另一個成員函數expired()的功能等價於use_count()==0,但更快,表示被觀測的資源(也就是shared_ptr的管理的資源)已經不復存在。weak_ptr可以使用一個非常重要的成員函數lock()從被觀測的shared_ptr獲得一個可用的shared_ptr對象, 從而操作資源。但當expired()==true的時候,lock()函數將返回一個存儲空指針的shared_ptr。

int main() {
	{
		shared_ptr<int> sh_ptr = make_shared<int>(10);
		cout << sh_ptr.use_count() <<endl;//1
		weak_ptr<int> wp(sh_ptr);//weak_ptr沒有共享資源,它的構造不會引起指針引用計數的增加
		cout << wp.use_count() << endl;//1
		cout << sh_ptr.use_count() << endl;//1
		if (!wp.expired()) {
			shared_ptr<int> sh_ptr2 = wp.lock(); //wp.lock()獲得一個sh_ptr觀測權去初始化sh_ptr2
			*sh_ptr = 100;
			cout << sh_ptr.use_count() << endl;//2
		}
	}
}

參考文章:https://www.cnblogs.com/wxquare/p/4759020.html

智能指針的設計和實現:

下面是一個簡單智能指針的demo。智能指針類將一個計數器與類指向的對象相關聯,引用計數跟蹤該類有多少個對象共享同一指針。每次創建類的新對象時,初始化指針並將引用計數置爲1;當對象作爲另一對象的副本而創建時,拷貝構造函數拷貝指針並增加與之相應的引用計數;對一個對象進行賦值時,賦值操作符減少左操作數所指對象的引用計數(如果引用計數爲減至0,則刪除對象),並增加右操作數所指對象的引用計數;調用析構函數時,構造函數減少引用計數(如果引用計數減至0,則刪除基礎對象)。智能指針就是模擬指針動作的類。所有的智能指針都會重載 -> 和 * 操作符。智能指針還有許多其他功能,比較有用的是自動銷燬。這主要是利用棧對象的有限作用域以及臨時對象(有限作用域實現)析構函數釋放內存。

#include<iostream>
#include<memory>
#include <assert.h>
using namespace std;

struct node {
	int t;
	char a;
};

template<typename T>
class shared_p {
	size_t *count;//計數
	T *ptr;//原對象指針
public:
	shared_p(T* t = nullptr) :ptr(t) {//構造函數,將外部指針賦給智能指針
		if (ptr) count = new size_t(1);
		else count = new size_t(0);
	}

	shared_p(const shared_p& t) {//拷貝構造函數
		if (this !=  &t) {//判斷是否是同一快內存區域
			this->ptr = t.ptr;//t是shared_p的對象所以使用 . 而this 是對象地址所以使用->
			this->count = t.count;
			(*this->count)++;//this是對象地址,不是對象本身
		}
	}

	shared_p& operator =(const shared_p &t) {//賦值,返回對象本身是爲了多重賦值a=b=c
		if (this->ptr == t.ptr) {
			return *this;
		}
		if ((*this->count) > 0) {
			(*this->count)--;
			if ((*this->count) == 0) {
				delete this->ptr;
				delete this->_count;
			}
		}
		this->ptr = t.ptr;
		this->count = t.count;
		(*this->count)++;
		return *this;
	}

	T& operator*() {//重載*返回對象
		return *(this->ptr);

	}

	T* operator->() {//重載->返回地址
		return this->ptr;
	}

	size_t use_count() {
		return *this->count;
	}

	~shared_p() {
		(*this->count)--;
		if (*this->count == 0) {
			delete this->ptr;
			delete this->count;
		}
	}
	
};


int main() {
	shared_p<node> sp(new node());//構造
	shared_p<node> sp0(sp);//拷貝構造
	shared_p<node> sp1 = sp;//賦值
	sp->a = 'a';
	cout << sp->a << endl;//a
	cout << (*sp1).a << endl;//a
	cout<<sp.use_count();//3

}

文章參考:https://www.cnblogs.com/wxquare/p/4759020.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章