淺析C++標準庫與boost庫中的智能指針

什麼是智能指針呢,它是行爲類似於指針的類對象,但這種對象還有其他功能。我們爲什麼要封裝智能指針類對象呢?這是因爲C++中的動態內存需要用戶自己來維護,動態開闢的空間,在出函數作用域或者程序正常退出前必須釋放掉,否則會造成內存泄漏,所以我們會定義一個類來封裝資源的分配和釋放,在構造函數完成資源的分配和初始化,在析構函數完成資源的清理,可以保證資源的正確初始化和釋放。

我們可以來看下面一段程序,這裏一段空間需要釋放多次,程序會十分繁瑣。並且若沒有及時釋放很容易出現資源泄露等問題。

#include<iostream>
using namespace std;

void FunTest1()
{
	throw 1;
}

bool FunTest2()
{
	return false;
}

void FunTest()
{
	int* p = new int[10];
	FILE* fp = fopen("1.txt", "rb");
	if(NULL == fp)
	{
		delete[] p;
		p = NULL;
		return;
	}

	try
	{
		FunTest1();
	}
	catch(...)
	{
		delete[] p;
		p = NULL;
		fclose(fp);
		throw;
	}

	if(!FunTest2())
	{
		delete[] p;
		p = NULL;
		fclose(fp);
		return;
	}

	delete[] p;
	p = NULL; 
	fclose(fp);
}

int main()
{
        FunTest();
	return 0;
}
首先我們來模擬實現C++標準庫中的AutoPtr。

#include<iostream>
using namespace std;

//第一版本AutoPtr
template<typename T>
class AutoPtr
{
public:
	AutoPtr(T* ap = 0)
		:_ptr(ap)
	{}

	AutoPtr(AutoPtr<T>& ap)//拷貝構造不能用const,因爲後續要改變ap._ptr的值
		:_ptr(ap._ptr)//將ap._ptr的管理權交給類中_ptr
	{
		ap._ptr == NULL;//置空ap
	}

	AutoPtr<T>& operator=(AutoPtr<T>& ap)//賦值運算符重載同樣不能用const修飾
	{
		if(this != &ap)
		{
			if(_ptr)
				delete _ptr;
				_ptr = ap._ptr;
				ap._ptr = NULL;
		}
		return *this;
	}

	~AutoPtr()
	{
		if(_ptr)
			delete _ptr;
		    _ptr = NULL;
	}
//基本操作
	T& operator*()
	{
		return *_ptr;
	}

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

private:
	T* _ptr;
};
int main()
{
	AutoPtr<int> ap1(new int(3));
	AutoPtr<int> ap2(ap1);//ap1將資源管理權限交給ap2
	*ap2 = 1;
	*ap1 = 2;//ap1已經將管理權限交出,此時對ap1進行解引用會出錯
	system("pause");
	return 0;
}
接下來來實現第二版本AutoPtr。

#include<iostream>
using namespace std;

//第二版本AutoPtr
template<typename T>
class AutoPtr
{
public:
	AutoPtr(T* ap)
		:_ptr(ap)
		,_owner(true)//若資源由自己管理則_owner爲true
	{
		if(_ptr==NULL)
			_owner =  false;
	}

	AutoPtr(const AutoPtr<T>& ap)//可以由const修飾,因爲指針的指向並不改變,只是將其_owner置爲false
	{
		_ptr = ap._ptr;
		ap._owner = false;
	}

	AutoPtr<T>& operator=(const AutoPtr<T>& ap)//同樣有const修飾
	{
		if(this != &ap)
		{
			delete _ptr;
			_ptr = ap._ptr;
			_owner = true;
			ap._ptr = NULL;
			ap._owner = false;
		}
		return *this;
	}

	~AutoPtr()
	{
		if(_owner)//對這塊資源有管理權限纔可以釋放
			delete _ptr;
	}
//對指針的基本操作
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
	mutable bool _owner;//可變的
};

int main()
{
	AutoPtr<int> ap1(new int);
	AutoPtr<int> ap2(ap1);
	*ap1 = 1;
	*ap2 = 2;
	system("pause");
	return 0;
}
上面這兩個AutoPtr雖然第二個比第一個好些,但是指針都指向同一段空間,如果在函數結束釋放空間則後續的訪問也會出錯。

所以又提出了ScopedPtr,這個智能指針採取比較暴力的手段,讓空間只由自己一個來管理。

我們可以看一下它是如何實現的。

//防拷貝
#include<iostream>
using namespace std;

template<class T>
class ScopedPtr
{
public:
	ScopedPtr(T* sp)
		:_ptr(sp)
	{}

	~ScopedPtr()
	{
		if(_ptr)
			delete _ptr;
		_ptr = NULL;
	}

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

	T* operator->()
	{
		return _ptr;
	}
	
private:
	ScopedPtr(ScopedPtr<T>& sp);//此處拷貝構造函數與賦值運算符重載都要設置成私有聲明
	ScopedPtr<T>& operator=(ScopedPtr<T>& sp);
	T* _ptr;
};
在這裏就需要介紹一下如何防拷貝,防拷貝有三種方法:

1.聲明成公有,這樣的話類外的人可以給出定義。

2.聲明成私有,但是把定義也給出,這種情況下友元函數可以調用,類內成員函數也可能調用。所以也不予採用。

3.聲明成私有,這種方法簡潔並且也能起到防拷貝的作用

接着我們繼續模擬實現ScopedArray:

template<typename T>
class ScopedArray
{
public:
	ScopedArray(T* s)
		:_ptr(s)
	{}

	~ScopedArray()
	{
		if(_ptr)
			delete[] _ptr;
	}

	T& operator[](int index)
	{
		return _ptr[index];
	}
private:
	ScopedArray(const ScopedArray<T>& psa);
	ScopedArray<T>& operator=(ScopedArray<T> psa);
	T* _ptr;
};
但是庫裏面並沒有引進ScopedArray這是因爲它類似於我們庫中的Vector數組,都是以順序方式存儲數據。

最後我們來模擬boost庫中shared_ptr,但這個函數線程不是很安全,一般建議使用scoped_ptr

#include<iostream>
using namespace std;

template<typename T>
class SharedPtr
{
public:
	SharedPtr(T* sp = 0)
		:_ptr(sp)
		,_count(new int(1))
	{}
	SharedPtr(const SharedPtr<T>& sp)
		:_ptr(sp._ptr)
		,_count(sp._count)
	{
		++(*_count);
	}
	SharedPtr<T>& operator=(const SharedPtr<T>& sp)
	{
		if(this != &sp)
		{
			if(0 == --(*_count) && _ptr)
			{
				delete _ptr;
			    _ptr = NULL;
				delete _count;
				_count = NULL;
			}
			_ptr = sp._ptr;
			_count = sp._count;
			++(*_count);
		}
		return *this;
	}
	~SharedPtr()
		{
			if(_ptr && 0 == --(*_count))
			{
				delete _ptr;
				_ptr = NULL;
				delete _count;
				_count = NULL;
			}
		}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
	int* _count;//開闢引用計數空間
};
int main()
{
	SharedPtr<int> sp1(new int(1));
	SharedPtr<int> sp2(sp1);
	SharedPtr<int> sp3;
	sp3 = sp2;
	*sp1 = 2;
	*sp2 = 3;
	*sp3 = 4;
	system("pause");
	return 0;
}

//定製刪除器
#include<boost/shared_ptr.hpp>
using namespace boost;

class FClose
{
public:
	void operator()(void* fp)
	{
		fclose((FILE*)fp);
	}
};

class Free
{
public:
	void operator()(void* p)
	{
		free(p);
	}
};
void FunTest()
{
	shared_ptr<FILE> p1(fopen("test.txt","r"),FClose());
	shared_ptr<int> p2((int*)malloc(sizeof(int)),Free());
}
int main()
{
	FunTest();
	system("pause");
	return 0;
}

後面還有比較重要的循環引用等問題將在下一篇博客介紹。




發佈了62 篇原創文章 · 獲贊 14 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章