天天看點

c++智能指針(上)

一、有關智能指針的基本資訊 (一)智能指針是RAII思想的一個産品 RAII(資源獲得即初始化),定義一個類來封裝資源的配置設定和釋放,在構造函數完成資源的配置設定和初始化,在析構函數完成資源的清理,可以保證資源的正确初始化和釋放。 所謂智能指針,就是智能化(自動化)管理指針所指向的動态資源的釋放。 (二)為什麼要有智能指針 eg1:

void Test1()
{
    int* p=new int(1);
    if(1)
    {
        return;
    }
    delete p;    //未執行
}
           

eg2:

void DoSomeTing()
{
    if(1)
    {
        throw 1;
    }
}
void Test2()
{
    int* p1=new int(2);
    DoSomeTing();
    delete p1;    //未執行
}
int main()
{
    try
    {
        Test2();
    }
    catch(...)
    {
        cout<<"未知異常"<<endl;
    }
    return 0;
}
           

要解決此類問題,就需要使用智能指針。

二、智能指針 Boost庫中的智能指針:

  1. 管理權轉移(帶缺陷的設計)——>auto_ptr
  2. 防拷貝(簡單粗暴)——>scoped_ptr
  3. 引用計數(更實用更複雜)——>shared_ptr

C++11庫中引入了unique_ptr(相當于scoped_ptr)、shared_ptr和weak_ptr。 (一)auto_ptr 先實作一個最簡單的智能指針AutoPtr:

template<class T>
class AutoPtr
{
public:
    AutoPtr(T* ptr)
        :_ptr(ptr)
    {}
    ~AutoPtr()
    {
        if(_ptr)
        {
            delete _ptr;
            _ptr=NULL;
        }
    }
protected:
    T* _ptr;
};
           

利用該智能指針,可以将上述的eg1和eg2改正為:

void Test1()
{
    int* p=new int(1);
    AutoPtr<int> ap(p);
    if(1)
    {
        return;
    }
}

void DoSomeTing()
{
    if(1)
    {
        throw 1;
    }
}
void Test2()
{
    int* p1=new int(2);
    AutoPtr<int> ap(p1);
    DoSomeTing();
}
int main()
{
    try
    {
        Test1();
        Test2();
    }
    catch(...)
    {
        cout<<"未知異常"<<endl;
    }
    return 0;
}
           

但這種智能指針是存在缺陷的。 如果将main函數換為下述内容,則會使程式崩潰。

int main()
{
    AutoPtr<int> ap1(new int(1));
    AutoPtr<int> ap2(ap1);
    return 0;
}
           

這是因為ap1和ap2兩個對象指向了同一個地方,導緻該地方要被析構兩次。 需要加入管理權轉移的功能(加入拷貝構造函數如下):

AutoPtr(AutoPtr<T>& ap)
    :_ptr(ap._ptr)
{
    ap._ptr=NULL;
}
//此時屬于淺拷貝,ap2(ap1)時,轉移後ap1被置空,再使用時程式崩潰。說明此時智能指針無法完全實作指針的作用。
//至于該構造沒有使用const,是因為_ptr不是const型的,因而設為const類型後無法指派。
//另外,一般傳引用時使用const是不希望改變原來的,但這裡需要修改,是以不加const
//(此時使用深拷貝也不可以,因為開辟兩塊空間的話就是兩個智能指針,而重載的目的是希望它是一個指針。)
           

最後,為了完整實作AutoPtr,還需要加入運算符重載函數:

AutoPtr<T>& operator=(AutoPtr<T>& ap)    //指派運算符重載
{
      if (this != &ap)
      {
             delete _ptr;
             _ptr = ap._ptr;
             ap._ptr = NULL;
      }
      return *this;
}
T& operator*()    //解引用操作符重載
{
         return*_ptr;
}
T* operator->()    //->操作符重載(管理的是結構體的智能指針時)
{
         return_ptr;
}
           

Auto_ptr雖然能夠通過管理權轉移解決多對象指向同一地方的問題,但仍是一種有缺陷的智能指針,很多時候不允許使用。 (二)scoped_ptr scoped_ptr的模拟實作如下:

template<class T>
class ScopedPtr
{
public:
    ScopedPtr(T* ptr)
        :_ptr(ptr)
    {}
    ~ScopedPtr()
    {
        if(_ptr)
        {
            cout<<"delete"<<_ptr<<endl;
            delete _ptr;
        }
    }
    T& operator*()
    {
        return *_ptr;
    }
    T* operator->()
    {
        return _ptr;
    }
protected:
    ScopedPtr(ScopedPtr<T>& sp);    //隻聲明不實作
    ScopedPtr<T>& operator=(ScopedPtr<T>& sp);    //隻聲明不實作
prtected:
    T* _ptr;
};
           

scoped_ptr為了防止拷貝,直接不允許拷貝,簡單粗暴,但也不完美。 (三)shared_ptr 對于多對象指向同一地方的問題,前面已經介紹了管理權轉移和防拷貝兩種方法,而shared_ptr介紹了第三種解決方法——引入一個引用計數對象。 對此方法可依據下圖進行了解:

c++智能指針(上)

shared_ptr的模拟實作如下:

template<class T>
class SharedPtr
{
public:
       SharedPtr(T* ptr)
              :_ptr(ptr)
              , _pCount(new long(1))
       {}
       ~SharedPtr()
       {
              if (--(*_pCount) == 0)
              {
                     delete _ptr;
                     delete _pCount;
              }
       }
       SharedPtr(SharedPtr<T>& sp)    //拷貝構造,需要注意使計數加1
              :_ptr(sp._ptr)
              , _pCount(sp._pCount)
       {
              ++(*_pCount);
       }
            SharedPtr<T>& operator=(SharedPtr<T>&sp)    //指派運算符的重載
            {
                         return*this;
            }
           T& operator*()    //重載*
           {
                        return*_ptr;
           }
           T* operator->()    //重載->
           {
                        return_ptr;
           }
           longUseCount()    //傳回引用計數的值,友善檢視,例如:  cout<<"sp1:"<<sp1.UseCount()<<endl;
           {
                        return*_pCount;
           }
protected:
       T* _ptr;
       long* _pCount;
};
void TestSharedPtr()
{
       SharedPtr<int> sp1(new int(1));
       SharedPtr<int> sp2(sp1);
}
int main()
{
       TestSharedPtr();
       system("pause");
       return 0;
}
           

但對于指派運算,其實有三種情況:

c++智能指針(上)

而上述的shared_ptr模拟實作代碼隻能解決前兩種,需要對拷貝構造函數進行改進。

SharedPtr<T>& operator=(SharedPtr<T>&sp)    //改進後的拷貝構造
       {
              if(_ptr != sp._ptr)
              {
                     if(--(*_pCount) == 0)
                     {
                           delete_ptr;
                           delete_pCount;
                     }
                     _ptr =sp._ptr;
                     _pCount =sp._pCount;
                     ++(*_pCount);
              }
              return*this;
       }
void TestSharedPtr()    //對上述三種情況進行測試,都可通過
{
       SharedPtr<int> sp1(new int(1));
       SharedPtr<int> sp2(sp1);
       SharedPtr<int> sp3(sp2);
       sp1 = sp1;
       sp1 = sp2;
       SharedPtr<int> sp4(new int(2));
       sp1 = sp4;
}
           

繼續閱讀