什麼是智能指針呢,它是行爲類似於指針的類對象,但這種對象還有其他功能。我們爲什麼要封裝智能指針類對象呢?這是因爲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;
}
後面還有比較重要的循環引用等問題將在下一篇博客介紹。