天天看點

智能指針一、智能指針的引入二、智能指針的實作原理三、四種智能指針

一、智能指針的引入

我們知道,棧是系統開辟并且系統進行釋放的,而堆是程式員手動開辟,手動釋放的。那麼如果程式員忘記手動釋放就會造成記憶體洩露,或者由于程式邏輯運作出現異常,導緻代碼過早傳回,沒有執行到free或者delete。那麼如何避免這種錯誤呢,是以引入了智能指針(手動開辟,系統回收)

智能指針是怎麼防止記憶體洩露的,如下代碼:

void func()
{
    shared_ptr<int> p1(new int(10));
    *p1 = 20;
}
           

看這個 func 函數,在函數局部使用了 new 開辟了堆記憶體,此時 func 函數不管是正常運作結束,或者是代碼抛出異常,那麼在出作用域之前,智能指針對象 p1 會進行析構,在析構函數裡面它就會把管理的堆記憶體給釋放掉,有效的防止了記憶體洩露。

在C++98标準,隻有一個智能指針 auto_ptr

在C++11标準,智能指針有unique_ptr、shared_ptr 、weak_ptr 不用auto_ptr(有缺陷)借鑒boost庫引用的 

二、智能指針的實作原理

智能指針是一個類,這個類的構造函數中傳入一個普通指針,析構函數中釋放傳入的指針。智能指針的類都是棧上的對象,是以當函數(或程式)結束時,類會自動調用析構函數,析構函數會自動釋放資源。是以智能指針的作用原理就是在函數結束時自動釋放記憶體空間,不需要手動釋放記憶體空間。

什麼是智能指針? 智能的管理指針所指向的動态資源的釋放。它是一個類,有類似指針的功能。 

三、四種智能指針

<1>   auto_ptr --- 所有權唯一,但所有權可以轉移

模拟實作:

#include<iostream>
using namespace std;

//在實作的時候不知道管理什麼樣的記憶體塊,是以用模闆來實作 
template<typename T>
class Auto_Ptr
{
public:
	//構造函數
	Auto_Ptr(T* ptr)
		:mptr(ptr)
	{}

	//拷貝構造
	Auto_Ptr(Auto_Ptr<T>& rhs)
	{
		mptr = rhs.Release();
	}

	//指派運算符重載
	Auto_Ptr<T>& operator=(Auto_Ptr<T>& rhs)
	{
		if (this != &rhs)//自指派的判斷
		{
			delete mptr;
			mptr = rhs.Release();//轉讓權限,取消了舊指針的所有權
		}
		return *this;
	}

	//析構函數
	~Auto_Ptr()
	{
		delete mptr;//Auto_Ptr不能用來管理數組
		mptr = NULL;
	}
	//解引用
	T& operator*()
	{
		return *mptr;
	}
	//調用
	T* operator->()
	{
		return mptr;//return (this->mptr);
	}
private:
//這個函數隻是把智能指針指派為空,但是它原來指向的記憶體并沒有被釋放,相當于它隻是釋放了對資源的所有權
	T* Release()//轉讓權限
	{
		T* tmp = mptr;
		mptr = NULL;
		return tmp;
	}
	T* mptr;
};
           

缺陷:

1.一個指針變量指向的空間不能由兩個auto_ptr管理,不然會析構兩次,使程式崩潰 --- 所有權唯一,不能共享記憶體塊

//錯誤
int *ap = new int;
auto_ptr<int> a1(ap);
auto_ptr<int> a2(ap);
           
智能指針一、智能指針的引入二、智能指針的實作原理三、四種智能指針

2、指派或拷貝構造将原指針的所有權轉移給新指針,會使得原指針懸空,直接編譯不會出錯,但解引用是會出現很多問題

基于這個原因,應該避免把auto_ptr放到容器中,因為算法對容器操作時,很難避免STL内部對容器實作了指派傳遞操作(出錯了也不易發現),這樣會使容器中很多元素被置為NULL。

auto_ptr<int> ap1(new int);
auto_ptr<int> ap2(ap1);
//寫上面兩句不會報錯,但是如果對舊指針進行操作,運作出錯
*ap1 = 20;//出錯
           
智能指針一、智能指針的引入二、智能指針的實作原理三、四種智能指針

ap2剝奪了ap1的所有權,當程式運作時通路ap1将會報錯。

3、auto_ptr不能用來管理數組,析構函數中用的是delete

<2>   unique_ptr  --- 所有權唯一且不能轉移

以将構造和拷貝構造放到私有下,來保證他的所有權唯一,所有權不能能轉移

它是( C++11引入的,前身是scoped_ptr,scoped_ptr是boost庫裡的),也不支援拷貝構造和指派,但比auto_ptr好,直接指派會編譯出錯(與auto_ptr最大的不同就是類内私有的聲明了拷貝構造函數和指派運算符重載,是針對auto_ptr的缺點而出現的)

模拟實作:

#include<iostream>
using namespace std;

template<typename T>
class Unique_Ptr
{
public:
	Unique_Ptr(T* ptr)
		:mptr(ptr)
	{}

	~Unique_Ptr()
	{
		delete mptr;
		mptr = NULL;
	}
	T& operator*()
	{
		return *mptr;
	}
	T* operator->()
	{
		return mptr;
	}
private:
	//将指派和拷貝構造寫到私有,禁止所有權轉移
	Unique_Ptr(Unique_Ptr<T>& _Right);
	Unique_Ptr<T>& operator=(Unique_Ptr<T>& _Right);
	T* mptr;
};
           

因為構造和拷貝構造放到私有下,是以再進行拷貝構造,編譯就會出錯,保證了他是以有權唯一

智能指針一、智能指針的引入二、智能指針的實作原理三、四種智能指針

缺陷:

1、所有權唯一,不能資料共享

設計層面保證所有權唯一了 ,但是外部控制也可以讓他指向同一塊記憶體--->程式崩潰

//錯誤
int* p = new int;
unique_ptr<int> up1(p);
unique_ptr<int> up2(p);
           

<3>   shared_ptr  ---  所有權不唯一,基于引用計數

資源可以被多個指針共享,它使用計數機制來表明資源被幾個指針共享。當我們調用release()時,目前指針會釋放資源所有權,計數減一。當計數等于0時,資源會被釋放。

智能指針一、智能指針的引入二、智能指針的實作原理三、四種智能指針

圖來自:如何回答C++面試中關于智能指針的問題?

shared_ptr是強引用指針 ,會有智能指針互相引用的問題。比如上圖的雙向連結清單

解決這種狀況的辦法就是将兩個類中的一個成員變量改為

weak_ptr

對象,因為

weak_ptr

不會增加引用計數,使得引用形不成環,最後就可以正常的釋放内部的對象,不會造成記憶體洩漏

<4>   weak_ptr  --- 和shared_ptr搭配使用

 引用計數有一個問題就是互相引用形成環,這樣兩個指針指向的記憶體都無法釋放。需要手動打破循環引用或使用weak_ptr。weak_ptr是一個弱引用,它是為了配合shared_ptr而引入的一種智能指針,它指向一個由shared_ptr管理的對象而不影響所指對象的生命周期,也就是說,它隻引用,不計數。如果一塊記憶體被shared_ptr和weak_ptr同時引用,當所有shared_ptr析構了之後,不管還有沒有weak_ptr引用該記憶體,記憶體也會被釋放。是以weak_ptr不保證它指向的記憶體一定是有效的,在使用之前需要檢查weak_ptr是否為空指針。

weak_ptr并沒有重載operator->和operator *操作符,是以不可直接通過weak_ptr使用對象,典型的用法是調用其lock函數來獲得shared_ptr示例,進而通路原始對象。

weak_ptr也維護了一個引用計數,跟shared_ptr維護的引用計數或互不幹擾,或互相協同。weak_ptr的指針會在weak_ptr維護的引用計數上加一,而shared_ptr會在shared_ptr維護的引用計數上加一,這樣在循環引用時,就會因為對不同引用的判斷的不同,使最終決定是否釋放空間的結果也不相同。

有一個規定,就是建立對象的時候,持有它的強智能指針,當其他地方想使用這個對象的時候,應該持有該對象的弱智能指針,

Q:如何判斷weak_ptr的對象是否失效? 

A:1、expired():檢查被引用的對象是否已删除。 

2、lock()會傳回shared指針,判斷該指針是否為空。 

3、use_count()也可以得到shared引用的個數,但速度較慢。

智能指針一、智能指針的引入二、智能指針的實作原理三、四種智能指針

(1)weak_ptr雖然是一個模闆類,但是不能用來直接定義指向原始指針的對象。

(2)weak_ptr接受shared_ptr類型的變量指派,但是反過來是行不通的,需要使用lock函數。

(3)weak_ptr設計之初就是為了服務于shared_ptr的,是以不增加引用計數就是它的核心功能。

(4)由于不知道什麼之後weak_ptr所指向的對象就會被析構掉,是以使用之前請先使用expired函數檢測一下。

參考部落格:C++智能指針——探究六個常見的智能指針的使用及原理

智能指針 auto_ptr、scoped_ptr、shared_ptr、weak_ptr

繼續閱讀