一直都知道智能指針是依靠引用計數的原理實現的,但是,自己一直都沒有嘗試自己實現一個簡單的指針指針,最近嘗試自己寫了一個智能指針的類,進行了簡單的測試,不知道會不會有bug,代碼如下:
SmartPointer.h
#include <iostream>
template <typename T>
class SmartPointer
{
public:
//構造函數
SmartPointer( T *p = 0 ):_ptr( p ), _reference_count ( new size_t ){
if( p )
*_reference_count = 1;
else
*_reference_count = 0;
}
//拷貝構造函數(淺拷貝)
SmartPointer( const SmartPointer& other )
{
if( this != &other )
{
_ptr = other._ptr;
_reference_count = other._reference_count;
(* _reference_count )++;
}
}
//重載賦值操作符
SmartPointer& operator= ( const SmartPointer& other )
{
if( _ptr == other._ptr )
return *this;
//先釋放原有所指向的對象
releaseCount();
_ptr = other._ptr;
_reference_count = other._reference_count;
//增加引用計數
( * _reference_count )++;
return *this;
}
//重載運算符
T& operator*()
{
if( _ptr )
return *_ptr;
}
//重載運算符
T* operator->()
{
if( _ptr )
return _ptr;
}
//析構函數
~SmartPointer()
{
if( -- ( *_reference_count ) == 0 )
{
delete _ptr;
delete _reference_count;
}
}
private:
T *_ptr;
size_t *_reference_count;//指針
void releaseCount(){
if( _ptr )
{
(* _reference_count )--;
if( (*_reference_count) == 0 )
{
delete _ptr;
delete _reference_count;
}
}
}
};
再介紹一下常用的智能指針:
1. std::auto_ptr,有很多問題。 不支持複製(拷貝構造函數)和賦值(operator =),但複製或賦值的時候不會提示出錯。因爲不能被複制,所以不能被放入容器中。
2. C++11或boost的shared_ptr,基於引用計數的智能指針。可隨意賦值,直到內存的引用計數爲0的時候這個內存會被釋放。
3. C++11或boost的weak_ptr,弱引用。 引用計數有一個問題就是互相引用形成環,這樣兩個指針指向的內存都無法釋放。需要手動打破循環引用或使用weak_ptr。顧名思義,weak_ptr是一個弱引用,只引用,不計數。如果一塊內存被shared_ptr和weak_ptr同時引用,當所有shared_ptr析構了之後,不管還有沒有weak_ptr引用該內存,內存也會被釋放。所以weak_ptr不保證它指向的內存一定是有效的,在使用之前需要檢查weak_ptr是否爲空指針。
4. C++11引入的unique_ptr, 也不支持複製和賦值,但比auto_ptr好,直接賦值會編譯出錯。實在想賦值的話,需要使用std::move。
例如:
std::unique_ptr<int> p1(new int(5));
std::unique_ptr<int> p2 = p1; // 編譯會出錯
std::unique_ptr<int> p3 = std::move(p1); // 轉移所有權, 現在那塊內存歸p3所有, p1成爲無效的指針.
談到智能指針互相引用成環的問題,下面舉個例子:
class B;
class A
{
public:
shared_ptr<B>
m_b;
};
class B
{
public:
shared_ptr<A>
m_a;
};
int main()
{
while (true)
{
shared_ptr<A>
a(new A);
//new出來的A的引用計數此時爲1
shared_ptr<B>
b(new B);
//new出來的B的引用計數此時爲1
a->m_b
= b; //B的引用計數增加爲2
b->m_a
= a; //A的引用計數增加爲2
}
//b先出作用域,B的引用計數減少爲1,不爲0,所以堆上的B空間沒有被釋放,且B持有的A也沒有機會被析構,A的引用計數也完全沒減少
//a後出作用域,同理A的引用計數減少爲1,不爲0,所以堆上A的空間也沒有被釋放
}
所以在使用基於引用計數的智能指針時,要特別小心循環引用帶來的內存泄漏,循環引用不只是兩方的情況,只要引用鏈成環都會出現問題。當然循環引用本身就說明設計上可能存在一些問題,如果特殊原因不得不使用循環引用,那可以讓引用鏈上的一方持用普通指針(或弱智能指針weak_ptr)即可