C++智能指針

爲什麼要有智能指針?

在C++中沒有垃圾回收機制,用戶需要自己來對已經申請的內存空間進行釋放,如果不釋放會存在內存泄漏問題。如果代碼拋異常,資源沒有回收也可能產生死鎖。

智能指針的使用

首先介紹一下RAII,它是Resource Acquisition Is Initialization,即資源獲取時就初始化,將資源使用方式用類的方式來進行封裝。在構造函數中將資源進行初始化,在析構函數中清理資源。
RAII是一種利用對象生命週期來控制程序資源(如內存,文件句柄,網絡連接,互斥量等等)的簡單技術。
在對象構造時獲取資源,接着控制對資源的訪問使之在對象的生命週期內始終保持有效,最後在
對象析構的時候釋放資源
。藉此,實際上是把管理一份資源的責任託管給了一個對象。
兩大好處:

  1. 不需要顯示地釋放資源。
  2. 採用這種方式,對象所需要的資源在其生命週期內始終保持有效。

智能指針的實現原理

1. 實現RAII思想
2. 像指針一樣 operator
() operator->()
3. 解決深淺拷貝
*

auto_ptr

namespace a{
	template<class T>
	class auto_ptr{
	public:
		auto_ptr(T* ptr=nullptr)
			:_ptr(ptr)
		{}

		~auto_ptr(){
			if (_ptr)
			{
				delete _ptr;
			}
		}
		auto_ptr(auto_ptr<T>& p)
		{
			_ptr = p._ptr;
			p._ptr = nullptr;
		}
		//實現RAII 思想,像指針一樣,解決深淺拷貝問題
		T* operator->()
		{
			return _ptr;
		}
		T& operator*()
		{
			return *_ptr;
		}

	private:
		T* _ptr;
	};
}

struct Date{
	int _year;
	int _month;
	int _day;
};
int main()
{
	a::auto_ptr<int> p1(new int(1));
	*p1=2;
	a::auto_ptr<int> p2(p1);
	//auto_ptr 的拷貝構造,是將資源進行轉移,當下次繼續對p1進行* 引用時,就會出錯!
	*p1 = 2;
	a::auto_ptr<Date> p3(new Date);
	return 0;
}

標準庫中建議無論什麼情況下都不要使用auto_ptr。

unique_ptr

由上面的代碼可知,auto_ptr 存在對已經釋放的資源解引用會出錯,則C++11提供了一種unique_ptr 它顧名思義,防止拷貝與賦值。就防止了資源的泄漏。

namespace b{
	template<class T>
	class unique_ptr{
	public:
		unique_ptr(T* ptr=nullptr)
			:_ptr(ptr)
		{}
		~unique_ptr()
		{
			if (_ptr)
			{
				delete _ptr;
			}
		}
		//像指針一樣
		T* operator->()
		{
			return _ptr;
		}
		T& operator*()
		{
			return *_ptr;
		}
		//c++11中防止拷貝的方式
		//函數後面加上delete
		//意思是禁止該函數生成,將默認成員函數刪除
		unique_ptr(const unique_ptr<T> &) = delete;
		unique_ptr<T>& operator=(unique_ptr<T>&)=delete
	private:
		//防止拷貝與防止運算符重載
		//c++98在私有中只聲明不定義
		//unique_ptr(const unique_ptr<T>& p);
		//unique_ptr<T>& operator = (unique_ptr<T> p);

	private:
		T* _ptr;
	};
}
struct Data{
	int _date;
	int _year;
	int _month;
};

int main()
{
	b::unique_ptr<int> p1(new int(1));
	 //b::unique_ptr<int> p2(p1);
	 //b::unique_ptr<int> p3(new int(3));
	 //p1 = p3;
	b::unique_ptr<Data> p2(new Data);
	p2->_date = 1;
	p2->_month = 2;
	p2->_year = 3;

	return 0;
}

share_ptr

支持拷貝,通過引用計數的方式來實現多個share_ptr對象之間共享資源

  1. share_prt 在其內部,給每個資源都維護了一份計數,來記錄該份資源被幾個對象共享。
  2. 在對象被銷燬時(也就是析構函數調用),就說明自己不使用該資源了,對象的引用計數減一。
  3. 如果引用計數是0,就說明自己還是最後一個使用該資源的對象,就必須釋放該資源
  4. 如果引用計數不是0,就說明除了自己還要其他對象在使用該份資源,不能釋放該資源,否則其他對象就成爲野指針了。
#include<mutex>
namespace c{
	template<class T>
	class share_ptr{
	public:
		share_ptr(T* ptr=nullptr)
			:_ptr(ptr)
			, _pCount(nullptr)
			, _mt(nullptr)
		{
			if (_ptr)
			{
				_pCount = new int(0);
				_mt = new mutex;
				++*_pCount;
			}
		}
		~share_ptr()
		{
			_mt->lock();
			if (_ptr&&--*_pCount==0)
			{
				delete _ptr;
			}
			_mt->unlock();
		}
		share_ptr(const share_ptr<T>& p)
			:_ptr(p._ptr)
			, _pCount(p._pCount)
		{
			*_pCount++;
		}
		share_ptr<T>& operator=(share_ptr<T>& p)
		{
			//賦值分情況
			if (this != &p)
			{
				_mt->lock();
				//被賦值的對象
				if (_ptr)
				{
					if (_ptr&&--*_pCount == 0)
					{
						delete _ptr;
						delete _pCount;
					}
				}
				_ptr = p._ptr;
				_pCount = p._pCount;
				++*_pCount;
				_mt->unlock();
			}
			return *this;
		}
		//像指針一樣
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}


	private:
		T* _ptr;
		int* _pCount;
		mutex* _mt;
	};
}
struct Date{
	int _day;
	int _year;
	int _month;
};

int main()
{
	c::share_ptr<int> p1(new int(1));
	c::share_ptr<int> p2(new int(2));
	c::share_ptr<int> p3;
	p3 = p1;
	p2 = p1;

	return 0;
}

上述代碼存在缺陷,1.++與–存在線程安全 2.不能管理任意類型的指針 3.循環引用

什麼是循環引用?
當用share_ptr 定義雙向鏈表的節點時,當兩個節點相互指向,此時兩個節點的引用計數都爲2,在兩個節點都釋放時,引用計數的值爲1,此時兩個節點還是相互指向,沒有釋放,形成了循環引用。

怎麼解決循環引用?
weak_ptr,將兩個節點的_pre,_next的指針用weak_ptr來定義,注意,weak_ptr必須搭配share_ptr來使用。

怎麼解釋線程不安全?
加鎖

解決管理任意類型的指針,構造刪除器!

//任意類型的資源能夠進行管理
//假設:資源可以new,malloc 文件指針,套接字
//定製刪除器


//針對new的資源
template<class T>
struct DFDel
{
	void operator()(T*& ptr)
	{
		if (ptr)
		{
			delete ptr;
			ptr == nullptr;
		}
	}
};



//對於文件資源
struct FClose{
	void operator()(FILE* pf)
	{
		if (pf)
		{
			fclose(pf);
		}
	}
};

//針對free的資源
template<class T>
struct Free
{
	void operator()(T*& ptr)
	{
		if (ptr)
		{
			free(ptr);
			ptr = nullptr;
		}
	}
};
namespace b{

	template<class T, class DF = DFDel<T>>
	class unique_ptr{
	public:
		unique_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}
		~unique_ptr()
		{
			DF(_ptr);
		}
		//像指針一樣
		T* operator->()
		{
			return _ptr;
		}
		T& operator*()
		{
			return *_ptr;
		}
		//c++11中防止拷貝的方式
		//函數後面加上delete
		//意思是禁止該函數生成,將默認成員函數刪除
		unique_ptr(const unique_ptr<T> &) = delete;
		unique_ptr<T>& operator=(unique_ptr<T>&) = delete
	private:
		//防止拷貝與防止運算符重載
		//c++98在私有中只聲明不定義
		//unique_ptr(const unique_ptr<T>& p);
		//unique_ptr<T>& operator = (unique_ptr<T> p);

	private:
		T* _ptr;
	};
}
struct Data{
	int _date;
	int _year;
	int _month;
};

int main()
{
	b::unique_ptr<int> p1(new int(1));
	//b::unique_ptr<int> p2(p1);
	//b::unique_ptr<int> p3(new int(3));
	//p1 = p3;
	b::unique_ptr<Data> p2(new Data);
	p2->_date = 1;
	p2->_month = 2;
	p2->_year = 3;
	b::unique_ptr<Data,Free<Data>> p4((Data*)malloc(sizeof(Data)));
	b::unique_ptr<char, Free<char>> p5((char*)malloc(sizeof(char)));

	return 0;
}

以unique_ptr 爲例構造刪除器!

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