- 介紹
智能指針是用來實現指針指向的對象的共享的。其實現的基本思想:
1.每次創建類的新對象時,初始化指針並將引用計數置爲1;
2.當對象作爲另一對象的副本而創建時,拷貝構造函數拷貝指針並增加與之相應的引用計數;
3.對一個對象進行賦值時,賦值操作符減少左操作數所指對象的引用計數(如果引用計數減至0,則刪除對象),並增加右操作數所指對象的引用計數;
4.調用析構函數時,減少引用計數(如果引用計數減至0,則刪除基礎對象);
4.重載“->”以及“*”操作符,使得智能指針有類似於普通指針的操作。
根據以上分析,首先可以得出下面的類模板原型。
template <class T>
class SmartPointer
{
public:
SmartPointer(T *p = 0);//構造函數
SmartPointer(const SmartPointer& src);//拷貝構造函數
SmartPointer& operator = (const SmartPointer& rhs);//賦值函數
T* operator -> ();//重載->
T& operator * ();//重載*
~SmartPointer();//析構函數
private:
void MinusRef()
{
//被其他成員函數所調用
if (--*m_pRef == 0)//自身的引用計數減1
{
//如果計數爲0,則釋放內存
delete m_ptr;
delete m_pRef;
}
}
T *m_ptr; //保存對象指針
size_t *m_pRef; //保存引用計數
};
上面的私有成員函數MinusRef將引用計數減1。如果引用計數減至0,則刪除m_ptr所指對象。根據前面的分析,MinusRef只被賦值函數以及析構函數使用。
下面說明各個成員的具體定義。
首先是構造函數與析構函數的定義。普通構造函數中,m_ptr與p指向同一塊內存,並初始化引用計數爲1。拷貝構造函數中與普通構造函數的不同之處爲引用計數需要加1。析構函數調用私有成員MinusRef對引用計數遞減,並且判斷是否需要釋放對象。代碼如下。
template<class T>
SmartPointer<T>::SmartPointer(T *p) //普通構造函數
{
m_ptr = p; //m_ptr與p指向同一內存
m_pRef = new size_t(1); //m_pRef初值爲1
}
template<class T>
SmartPointer<T>::SmartPointer(const SmartPointer<T>& src)//拷貝構造函數
{
m_ptr = src.m_ptr; //m_ptr與src.m_ptr指向同一內存
m_pRef++;
m_pRef = src.m_pRef; //拷貝引用計數
}
template<class T>
SmartPointer<T>::~SmartPointer() //析構函數
{
MinusRef(); //引用減1,如果減後的引用爲0,則釋放內存
std::cout<<"SmartPointer: Destructor"<<std::endl;
}
接下來是“->”和“*”的重載。這兩個函數很簡單,只需要分別返回m_ptr以及m_ptr所指的內容即可。注意,如果m_ptr此時爲空,則應該拋出異常。代碼如下。
template<class T>
T* SmartPointer<T>::operator -> () //重載 ->
{
if (m_ptr)
return m_ptr;
//m_ptr爲NULL時,拋出異常
throw std::runtime_error("access through NULL pointer");
}
template<class T>
T& SmartPointer<T>::operator * () //重載 *
{
if (m_ptr)
return *m_ptr;
//m_ptr爲NULL時,拋出異常
throw std::runtime_error("dereference of NULL pointer");
}
最後是賦值函數的實現:
template<class T>
SmartPointer<T>& SmartPointer<T>::operator = (const SmartPointer<T>& rhs)//賦值函數
{
++*rhs.m_pRef; //rhs的引用加1
MinusRef(); //自身指向的原指針的引用減1
m_ptr = rhs.m_ptr; //m_ptr合rhs.m_ptr指向同一個對象
m_pRef = rhs.m_pRef; //複製引用
return *this;
}
這樣,就可以像 std::auto_ptr那樣來使用SmartPointer。以下先定義一個測試類,測試程序如下。
class Test
{
public:
Test() {name = NULL;}
Test(const char* strname)//構造函數
{
name = new char[strlen(strname)+1];//分配內存
strcpy(name, strname);//拷貝字符串
}
Test& operator = (const char *namestr)//賦值函數
{
if (name != NULL)
{
delete name;//釋放原來的內存
}
name = new char[strlen(namestr)+1];//分配新內存
strcpy(name, namestr);//拷貝字符串
return *this;
}
void ShowName() {cout << name << endl;}
~Test()
{
if (name != NULL)
{
delete name;
}
name = NULL;
cout << "delete name" << endl;
}
public:
char *name;
};
int _tmain(int argc, _TCHAR* argv[])
{
SmartPointer<Test> t1;//空指針
SmartPointer<Test> t2(new Test("fefd"));
SmartPointer<Test> t3(new Test("wewew"));
try
{
t1->ShowName();//空指針調用拋出異常
}
catch (const exception& err)
{
cout << err.what() << endl;
}
t2->ShowName(); //使用t2調用showName()
*t2 = "David"; //使用t2給對象賦值
t2->ShowName(); //使用t2調用showName()
t2 = t3; //賦值,原來t2的對象引用爲0,發生析構
//而t3的對象引用加1
cout << "End of main..." << endl;
getchar();
return 0;
}
main函數代碼第41行,t1指向一個NULL指針,因此調用ShowName時會出現異常(異常在重載的“->”函數中被拋出)。
main函數代碼第48~50行,使用SmartPtr對象對Test對象進行操作,其方法與使用Test對象指針的操作方法相同。
main函數代碼第51行,對t2進行賦值操作,操作完成後,t2引用的原對象發生析構(此對象沒有SmartPtr對象引用了),t2和t3引用同一個對象,於是這個對象的引用計數加1。注意,這裏我們並沒有顯示地對t2所引用的原對象進行釋放操作,這就是智能指針的精髓所在。