C++之智能指針

本文轉自:

http://blog.csdn.net/wxt_hillwill/article/details/69950608

智能指針

     智能指針(smart pointer)是存儲指向動態分配(堆)對象指針的類,用於生存期控制,能夠確保自動正確的銷燬動態分配的對象,防止內存泄露。智能指針就是模擬指針動作的類。所有的智能指針都會重載 -> 和 * 操作符。智能指針還有許多其他功能,比較有用的是自動銷燬。這主要是利用棧對象的有限作用域以及臨時對象(有限作用域實現)析構函數釋放內存。當然,智能指針還不止這些,還包括複製時可以修改源對象等。智能指針根據需求不同,設計也不同:

         auto_ptr         管理權轉讓(不建議使用)

       scoped_ptr     防拷貝

       shared_ptr      引用計數


模擬實現auto_ptr

    實現思路:AutoPtr的成員變量主要有T*  _ptr, bool _owner,主要實現原理是在構造對象時賦予其空間的所有權,在析構函數中通過_owner的真假來釋放所有權,並且在拷貝或賦值後將_owner設爲false,轉移空間的所有權。但此做法的問題是:如果要釋放一個拷貝出來的對象,雖然原對象的_owner爲false,但會多次釋放同一塊空間,導致內存泄漏。

template<class T>  
class AutoPtr  
{  
public:  
    AutoPtr(T* ptr = NULL)  
        :_ptr(ptr)  
        , _owner(true)  
    {}  
   
    AutoPtr(const AutoPtr<T>& ap)  
        :_ptr(ap._ptr)  
    {  
        ap._owner = false;  
        _owner = true;  
    }  
   
    AutoPtr<T>& operator=(const AutoPtr<T>& ap)  
    {  
        if (&s != this)  
        {  
            delete _ptr;  
            _ptr = ap._ptr;  
            ap._owner = false;  
            _owner = true;  
        }  
        return *this;  
    }  
   
    ~AutoPtr()  
    {  
        if (_ptr)  
        {          
            delete _ptr;  
            _ptr = NULL;  
            _owner = false;  
        }  
    }  
   
    T* operator->()  
    {  
        return _ptr;  
    }  
   
    T& operator*()  
    {  
        return *_ptr;  
    }  
  
private:  
    T* _ptr;  
    bool _owner;  
};  


         問題是:用ap1拷貝構造ap2,ap1和ap2指向同一塊空間,出了作用域之後,ap2會釋放空間還給系統,但ap1仍然指向這塊空間,雖然它的_owner已經改爲false。

     改進後還是管理空間的所有權轉移,但這種方法裏沒有_owner。構造和析構函數的實現方法類似,但拷貝和賦值後直接將_ptr賦爲空,禁止其再訪問原來的空間。

template<class T>    
class AutoPtr    
{    
public:    
    AutoPtr(T* ptr)    
        :_ptr(ptr)    
    {}    
  
    AutoPtr(AutoPtr<T> &ap)  //拷貝後將原有指向同一塊內存的指針置空  
        :_ptr(ap._ptr)    
    {    
        ap._ptr = NULL;    
    }    
  
    AutoPtr<T>& operator=(AutoPtr<T> &ap)    
    {    
        if (this != &ap)    
        {    
            delete _ptr;    
            _ptr = ap._ptr;    
            ap._ptr = NULL;    
        }    
        return *this;    
    }    
          
    T& operator*()    
    {    
        return *_ptr;    
    }    
  
    T* GetPtr()//返回原指針_ptr    
    {    
        return  _ptr;    
    }    
  
    T* operator->()    
    {    
        return _ptr;    
    }    
  
    ~AutoPtr()    
    {    
        cout << "~AutoPtr()" << endl;    
        if (_ptr)    
        {    
            delete _ptr;    
            _ptr = NULL;    
        }    
    }    
  
private:    
    T* _ptr;    
};   



模擬實現ScopedPtr

   因爲智能指針容易出現拷貝時釋放兩次的情況,所以ScopedPtr主要是爲了防止拷貝,防止拷貝的兩條必要條件是:①設置保護限定符(將拷貝構造函數和賦值運算符的重載設置成保護或者私有的,這樣就可以保證其他人在不知情的情況下定義拷貝構造和賦值運算符重載了);②對拷貝構造函數和賦值運算符重載只聲明不定義(如果不這麼做,編譯器就會自動合成拷貝構造和賦值運算符重載,就實現不了防拷貝的目的了)。

template<class T>    
class ScopedPtr    
{    
public:    
    ScopedPtr(T* ptr)    
        :_ptr(ptr)    
    {}    
  
    T& operator*()    
    {    
        return *_ptr;    
    }    
  
    T* GetPtr()//返回原指針_ptr    
    {    
        return  _ptr;    
    }    
  
    T* operator->()    
    {    
        return _ptr;    
    }    
  
    ~ScopedPtr()    
    {    
        cout << "~ScopedPtr()" << endl;    
        if (_ptr)    
        {    
            delete _ptr;    
            _ptr = NULL;    
        }    
    }    
  
protected://防拷貝:此處爲privated或protecte;拷貝構造和賦值函數只聲明不定義    
    ScopedPtr(const ScopedPtr<T> &sp);    
    ScopedPtr<T>& operator==(const ScopedPtr<T> &sp);    
  
private:    
    T* _ptr;    
};   



模擬實現SharedPtr

     SharedPtr顧名思義就是共享指針,思想是引用計數。引入指針變量pCount,指向同一塊空間對其計數,當只有一個指針指向空間時再釋放空間,這樣可以解決多個指針指向同一塊空間釋放多次導致內存泄漏的問題。

template<class T>  
class SharedPtr  
{  
public:  
    SharedPtr(T* ptr)  
        :_ptr(ptr)  
        , _pCount(new long(1))  
    {}  
   
    SharedPtr()  
        :_ptr(NULL)  
        , _pCount(new long(1))  
    {}  
   
    SharedPtr<T>(const SharedPtr<T>& sp)  
        : _ptr(sp._ptr)  
        , _pCount(sp._pCount)  
    {  
        ++(*_pCount);  
    }  
   
    SharedPtr<T>& operator=(const SharedPtr<T>& sp)  
    {  
        if (&sp != this)  
        {  
            if (--(*_pCount) == 0)  
            {  
                delete _ptr;  
                delete _pCount;  
            }  
            _ptr = sp._ptr;  
            _pCount = sp._pCount;  
            ++(*_pCount);  
        }  
        return *this;  
    }  
   
    ~SharedPtr()  
    {  
        if (_ptr)  
        {  
            if (--(*_pCount) == 0)  
            {  
                delete _ptr;  
                delete _pCount;  
            }  
        }  
    }  
   
    T& operator*()  
    {  
        return *_ptr;  
    }  
   
    long GetCount()  
    {  
        return *(_pCount);  
    }  
   
    T* GetPtr()  
    {  
        return _ptr;  
    }  
   
private:  
    T* _ptr;  
    long* _pCount;  
};  

      簡化版的引用計數智能指針SharedPtr還存在問題:①引用計數更新存在線程安全問題;②循環引用問題:③定製 刪除器

     線程安全問題:“Boost”文檔對於shared_ptr的線程安全有一段專門的描述,總結爲以下幾點:①同一個         shared_ptr被多個線程“讀”是安全的;②同一個shared_ptr被多個線程“寫”是不安全的;③共享引用計數的不同的shared_ptr被多個線程“寫”是安全的。

     循環引用問題:下面的例子中,p1和p2的引用計數都是1,進行析構p1時首先考慮釋放p2的空間,而釋放p2又 依賴於釋放p1的空間。這樣就形成了無限循環,最終造成內存泄漏。

using namespace boost;  
template <typename T>  
struct ListNode  
{  
    ~ListNode()  
    {  
        cout<<"ListNode()"<<endl;  
    }  
  
    shared_ptr<ListNode<T>> _prev;  
    shared_ptr<ListNode<T>> _next;  
};  
  
void Test()  
{  
    shared_ptr<ListNode> p1(new ListNode());  
    shared_ptr<ListNode> p2(new ListNode());  
    cout<<"p1->Count:"<<p1.use_count()<<endl;  
    cout<<"p2->Count:"<<p2.use_count()<<endl;  
    p1->_next = p2;  
    //p1結點的_next指向p2  
    p2->_prev = p1;  
    //p2結點的_prev指向p1  
    cout<<"p1->Count:"<<p1.use_count()<<endl;  
    cout<<"p2->Count:"<<p2.use_count()<<endl;  
}  
  
int main()  
{  
    Test();  
    system("pause");  
    return 0;  
}  

      這個問題的解決方法是:引入弱引用智能指針weak_ptr來打破循環引用。當至少有一個強引用share_ptr時,被   引用的對象就不能被釋放。而弱引用weak_ptr僅僅是當被引用對象存在時的一個引用。弱引用並不修改該對象的引用計數,這意味這弱引用它並不對對象的內存進行管理,在功能上類似於普通指針。並且弱引用能檢測到所管理的對象是否已經被釋放,從而避免訪問非法內存。使用方法如下:

using namespace boost;  
template <typename T>  
struct ListNode  
{  
    ~ListNode()  
    {  
        cout<<"ListNode()"<<endl;  
    }  
  
    weak_ptr<ListNode<T>> _prev;  
    weak_ptr<ListNode<T>> _next;  
};  


     定製刪除器:如果指針是一個指向文件類型的,在析構函數中只需關閉文件即可,而不是釋放空間;如果空間是 通過malloc開闢出來的,那麼在析構函數中要調用free函數,而不是delete操作符。上述問題通過仿函數就可以解決。具體代碼如下:
template<class T>  
class Del  
{  
public:  
    void operator() (const T *ptr)  
    {  
        delete ptr;  
    }  
};  
//仿函數實現FClose和Free  
struct FClose  
{  
    void operator() (void* ptr)  
    {  
        cout << "Close" << endl;  
        fclose((FILE*)ptr);  
    }  
};  
struct Free  
{  
    void operator() (void* ptr)  
    {  
        cout << "Free" << endl;  
        free(ptr);  
    }  
};  
  
template<class T,class Deleter=Del<T>>  
class SharedPtr  
{  
public:  
    SharedPtr(T* ptr)  
        :_ptr(ptr)  
        , _pCount(new long(1))  
    {}  
    SharedPtr(T* ptr,Deleter del)  
        :_ptr(ptr)  
        , _pCount(new long(1))  
        , _del(del)  
    {}  
    SharedPtr<T>& operator=(SharedPtr<T> sp)  
    {  
        swap(_ptr, sp._ptr);  
        swap(_pCount, sp._pCount);  
        return *this;  
    }  
    ~SharedPtr()  
    {  
        cout << "~SharedPtr()" << endl;  
        Release();  
    }  
    T& operator*()  
    {  
        return *_ptr;  
    }  
    T* GetPtr()//返回原指針_ptr  
    {  
        return  _ptr;  
    }  
    T* operator->()  
    {  
        return _ptr;  
    }  
    void Release()//釋放內存  
    {  
        if (--(*_pCount) == 0)  
        {  
            _del(_ptr);  
            delete _pCount;  
        }  
    }  
private:  
    T* _ptr;  
    long* _pCount;  
    Deleter _del;  
};  
  
void Test()  
{  
    SharedPtr<int> sp(new int(6));  
    SharedPtr<FILE, FClose> sp1(fopen("test.text", "w"), FClose());  
    SharedPtr<int, Free> sp2((int*)malloc(sizeof(int)* 6), Free());  
}  




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