1. 程式人生 > >【C++】智慧指標

【C++】智慧指標

本篇博文旨在介紹C++中的智慧指標;從為什麼引入它開始,分別實現了auto_ptr,scoped_ptr,unique_ptr,shared_ptr等智慧指標;介紹了各個智慧指標的特點;最後用防函式和智慧指標實現了檔案指標的管理

智慧指標概念

智慧指標是一個類,用它的物件管理著申請的記憶體空間,並通過作用域、生命週期來保證申請記憶體的釋放,從而防止出現記憶體洩漏

記憶體洩漏舉例

#include<iostream>
using namespace std;

//為什麼需要智慧指標
//每次返回都需要對指標進行處理
//很是麻煩
//所以要引出智慧指標,可以自出進行記憶體的釋放
void FunTest()
{
	int* p = new int[10];
	FILE* pFile = fopen("1.txt", "r");

	if (pFile == NULL)//如果開啟失敗,那麼p就成為了一個野指標
	{
		return;
	}

	if (p != NULL)
	{
		delete[] p;
		p = NULL;
	}
}
在上述程式碼中,如果檔案開啟失敗,那麼p指標就沒有進行釋放,會造成記憶體洩漏

如果我們每次在返回語句前進行釋放,當前面new申請的空間很多時,會不會太麻煩了

智慧指標

1、RAII

RAII稱為“資源獲取就是初始化”,是c++等程式語言常用的管理資源

避免記憶體洩露的方法。它保證在任何情況下,

使用物件時先構造物件,最後析構物件。

2、boost庫

瞭解智慧指標就需要了解boost庫,C++11引入的智慧指標標準中,參考了boost庫中對智慧指標的實現

模擬實現智慧指標

1、autoptr——不推薦使用的智慧指標

template<typename T>
class AutoPtr
{
public:
	AutoPtr(T* ap = NULL)
		:_p(ap)
	{
		ap = NULL;
		cout << "AutoPtr()" << endl;
	}

	AutoPtr(AutoPtr& ap)
		:_p(ap._p)
	{
		ap._p = NULL;
		//ap = NULL;
	}

	AutoPtr& operator=(AutoPtr&ap)
	{
		if (this != &ap)
		{
			if (_p != NULL)
			{
				delete _p;
				_p = ap._p;
				ap._p = NULL;
			}
		}

		return *this;
	}

	~AutoPtr()
	{
		if (_p != NULL)
		{
			cout << "~AutoPtr()" << endl;
			delete[] _p;
			_p = NULL;
		}
	}

	T* Get()
	{
		return _p;
	}

	T& operator*()
	{
		return *_p;
	}

	T* operator->()
	{
		return _p;
	}

private:
	T* _p;
};
auto_ptr的缺陷:

只能由一個物件來管理空間

當多個auto_ptr指向同一塊空間時,會由於多次釋放而導致崩潰

2、ownerptr——設定布林變數來表示有沒有管理空間

當進行拷貝構造,進行管理許可權的交換

只有掌握空間許可權的才可以進行釋放

template <typename T>
class OwnerPtr
{
public:
	//建構函式
	OwnerPtr(T* ap = NULL)
		:_p(ap)
		, _owner(true)
	{
		cout << "OwnerPtr()" << endl;
	}

	//拷貝建構函式
	OwnerPtr(OwnerPtr<T>& ap)
		:_p(ap._p)
		, _owner(true)
	{
		ap._owner = false;
	}

	OwnerPtr<T>& operator=(OwnerPtr<T>& ap)
	{
		if (this != &ap)
		{
			if (_p != NULL)
			{
				delete _p;
				_p = ap._p;
				_owner = true;
				ap._owner = false;
			}
		}
		return *this;
	}

	~OwnerPtr()
	{
		if (_owner && _p != NULL)
		{
			cout << "~OwnerPtr()" << endl;
			delete _p;
			_p = NULL;
			_owner = false;//false代表釋放完畢
		}
	}
	T* Get()
	{
		return _p;
	}
	T* operator*()
	{
		return *_p;
	}
	T* operator->()
	{
		return _p;
	}
private:
	T* _p;
	bool _owner;//表示物件是否佔用資源
};

3、scopedptr——最容易實現的智慧指標

scoped_ptr直接不允許進行拷貝以及賦值運算子過載,故而沒有釋放兩次導致崩潰的事情發生

template<typename T>
class ScopedPtr
{
public:
	ScopedPtr(T* ap = NULL)
		:_p(ap)
	{}

	~ScopedPtr()
	{
		if (_p != NULL)
		{
			delete _p;
			_p = NULL;
		}
	}

	T* Get()
	{
		return _p;
	}

	T& operator*()
	{
		return *_p;
	}

	T* operator->()
	{
		return _p;
	}
private:
	ScopedPtr(const ScopedPtr& ap);
	ScopedPtr<T>& operator=(const ScopedPtr& ap);
	//下面是另一種防止拷貝的方法
	/*ScopedPtr(const ScopedPtr& ap) = delete;
	ScopedPtr<T>& operator=(const ScopedPtr& ap) = delete;*/
protected:
	T* _p;
};

4、uniqueptr——類似於一個動態開闢的陣列

由於可以用vector實現,所以用的不多

並且不存在於boost庫

//unique_ptr
//過載了[]
//就比如一個vecotr開闢的動態陣列
template<typename T>
class UniquePtr
{
public:
	UniquePtr(T* ap)
		:_p(ap)
	{}

	T& operator[](const size_t index)
	{
		return _p[index];
	}

	const T& operator[](const size_t index)const 
	{
		return _p[index];
	}

	T* Get()
	{
		return _p;
	}
protected:
	T* _p;
};

void Funtest()
{
	UniquePtr<int> p(new int[5]);
	p[0] = 1;
	p[1] = 2;
	p[2] = 3;
	p[3] = 4;
	p[4] = 5;
	for (size_t i = 0; i<5; i++)
	{
		cout << p[i] << " ";
	}
}

5、sharedptr——引用計數版本的只能指標

通過對管理一塊空間的智慧指標進行計數,只當_pCount == 0 的時候再進行釋放

否則就 --_pCount

template<typename T>
class SharedPtr
{
public:
	SharedPtr(T* _ap)
		:_p(ap)
		, _pCount(NULL)
	{
		//當物件不為空時,對_pCount進行引用計數統計
		if (_p != NULL)
		{
			_pCount = new int[1];
		}
	}

	SharedPtr(SharedPtr<T> &ap)
		:_p(ap._p)
		, _pCount(ap._pCount)
	{
		//如果物件不為空
		//則增加引用計數
		if (p != NULL)
		{
			++(*_pCount);
		}
		sp._p = NULL;
	}

	SharedPtr<T> operator=(SharedPtr<T> &ap)
	{
		if (this != &ap)
		{
			//沒有管理空間
			if (_pCount == NULL)
			{
				_p = ap._p;
				_pCount = ap._pCount;

				if (_pCount != NULL)
					++(*_pCount);
			}
			else if (*_pCount == 1)//獨自管理一段空間
			{
				delete _p;
				delete _pCount;

				_p = ap._p;
				_pCount = ap._pCount;
				if (_pCount != NULL)
					++(*_pCount);
			}
			else//和別人共享空間
			{
				--(*_pCount);

				_p = ap._p;
				_pCount = ap._pCount;
				if (_pCount != NULL)
					++(*_pCount);
			}
		}
		return *this;
	}
protected:
	T* _p;
	int* _pCount;
};

6、sharedptr的迴圈引用解決方法---weakptr

sharedptr的迴圈引用

template<typename T>
class Node
{
public:
	Node(const T& value)
		: _value(value)
	{
		cout << "Node()" << endl;
	}
	
	~Node()
	{
		cout << "~Node()" << endl;
		cout << "this:" << this << endl;
	}

	shared_ptr<Node<T>> _pNext;
	shared_ptr<Node<T>> _pPre;
	T _value;
};

void TestSharedPtr()
{
	shared_ptr<Node<int>> s1(new Node<int>(1));
	shared_ptr<Node<int>> s2(new Node<int>(2));

	cout << "s1-use_count:" << s1.use_count() << endl;
	cout << "s2-use_count:" << s2.use_count() << endl;

	s1->_pNext = s2;
	s2->_pPre = s1;

	cout << "s1-use_count:" << s1.use_count() << endl;
	cout << "s2-use_count:" << s2.use_count() << endl;
}

我們看到,當 s1->_pNext = s2; s2->_pPre = s1; 時

他們各自的引用計數會自增

這樣就會導致釋放的時候  use_count -1 = 1;並不會釋放

就造成了記憶體洩漏


利用weakptr解決迴圈引用問題

template<typename T>
class Node
{
public:
	Node(const T& value)
		: _value(value)
	{
		cout << "Node()" << endl;
	}
	
	~Node()
	{
		cout << "~Node()" << endl;
		cout << "this:" << this << endl;
	}

	weak_ptr<Node<T>> _pNext;
	weak_ptr<Node<T>> _pPre;
	T _value;
};

void TestSharedPtr()
{
	shared_ptr<Node<int>> s1(new Node<int>(1));
	shared_ptr<Node<int>> s2(new Node<int>(2));

	cout << "s1-use_count:" << s1.use_count() << endl;
	cout << "s2-use_count:" << s2.use_count() << endl;

	s1->_pNext = s2;
	s2->_pPre = s1;

	cout << "s1-use_count:" << s1.use_count() << endl;
	cout << "s2-use_count:" << s2.use_count() << endl;
}

實現檔案指標釋放的兩種該方法

1、函式指標

//利用函式實現檔案指標的關閉
void FClose(FILE* pf)
{
	if (pf)
		fclose(pf);
}

void FunTest2()
{
	FILE* p = fopen("1.txt", "r");
	shared_ptr<FILE> sp(p, FClose);
}

2、利用防函式定製刪除器

//利用防函式來控制檔案指標的關閉
class FClose//定製刪除器
{
	void operator()(FILE* p)
	{
		if (p)
			fclose(p);
	}
};

void FunTest3()
{
	FILE* p = fopen("1.txt", "r");
	shared_ptr<FILE> sp(p, FClose);
}