天天看點

shared_ptr的使用和陷阱 shared_ptr的陷阱

shared_ptr的使用

配置設定記憶體

  • make_shared
    //make_shared<int>配置設定一塊int類型大小的記憶體,并值初始化為100
    //傳回值是shared_ptr類型,是以可以直接指派給sp
    shared_ptr<int> sp = make_shared<int>(100);
               
  • new

    接受指針參數的隻能指針構造函數是explicit的,是以,我們不能将一個内置指針隐式轉化為一個隻能指針,必須使用直接初始化形式

    //錯誤! 不會進行隐式轉換,類型不符合
    shared_ptr<int> sp1 = new int(100);
    //正确,直接初始化調用構造函數
    shared_ptr<int> sp2(new int(100000));
               

shared_ptr的操作

配置設定好了記憶體空間,我們就可以使用shared_ptr定義的操作了

  • p.get() 

    傳回p儲存的指針

  • swap(p,q) 

    交換p、q中儲存的指針

  • shared_ptr<T> p(q) 

    p是q的拷貝,它們指向同一塊記憶體,互相關聯

  • p = q 

    用q為p指派,之後p、q指向同一塊記憶體,q引用計數+1,p(原來記憶體空間的)引用計數-1

  • p.use_count() 

    傳回與p共享對象的智能指針數量

  • shared_ptr<T> p(q,d) 

    q是一個可以轉換為T*的指針,d是一個可調用對象(作為删除器),p接管q所指對象的所有權,用删除器d代替delete釋放記憶體

  • p.reset() 

    将p重置為空指針

  • p.reset(p) 

    将p重置為p(的值)

  • p.reset(p,d) 

    将p重置為p(的值)并使用d作為删除器

shared_ptr 關聯與獨立

多個共享指針指向同一個空間,它們的關系可能是關聯(我們所期望的正常關系)或是獨立的(一種錯誤狀态)

shared_ptr<int> sp1(new int(10));
    shared_ptr<int> sp2(sp1), sp3;
    sp3 = sp1;
    //一個典型的錯誤用法
    shared_ptr<int> sp4(sp1.get()); 
    cout << sp1.use_count() << " " << sp2.use_count() << " " 
    << sp3.use_count() << " " << sp4.use_count() << endl;
    //輸出 3 3 3 1
           

sp1,sp2,sp3是互相關聯的共享指針,共同控制所指記憶體的生存期,sp4雖然指向同樣的記憶體,卻是與sp1,sp2,sp3獨立的,sp4按自己的引用計數來關聯記憶體的釋放。

隻有用一個shared_ptr為另一個shared_ptr指派時,才将這連個共享指針關聯起來,直接使用位址值會導緻各個shared_ptr獨立。

向shared_ptr傳遞删除器

有時候我們需要用智能指針管理非new的對象,或者是沒有析構函數的類,由于shared_ptr預設使用delete來釋放記憶體并執行析構函數,對于以上的兩種情況是不适用的,是以我們要傳遞特别的删除器

删除器必須接受單個類型為 T* 的參數
//沒有析構函數的類
struct MyStruct
{
    int *p;
    MyStruct():p(new int(10)) { }   //構造函數中申請了一塊記憶體
                                    //用裸指針管理,不用時需要手動釋放
};

void main()
{
    //st是局部的對象,存放在棧區
    //并非由new申請,不可用delete釋放記憶體
    MyStruct st;
        //一個作用域
        {
            shared_ptr<MyStruct> sp(&st, [](MyStruct *ptr) {
                delete(ptr->p);
                ptr->p = nullptr;
                cout << "destructed." << endl;
            });
        }
        // 離開作用域,調用傳遞的删除器釋放sp所指的記憶體空間
}
           

對于以上這個例子,首先不可以用delete來釋放局部對象,然後MyStruct也沒有析構函數來釋放申請的空間,是以向管理它的shared_ptr傳遞一個删除器來做這兩件事。

shared_ptr的陷阱

不要寫出獨立的shared_ptr

關于獨立的shared_ptr的意思及危害上面已經說出,遵守下面幾點來避免這個錯誤

  1. 不要與裸指針混用
    //錯誤場景1
    int *x(new int(10));
    shared_ptr<int> sp1(x);
    shared_ptr<int> sp2(x);
    //雖然sp1、sp2都指向x所指的記憶體,但他們是獨立的,
    //會在其他shared_ptr還在使用記憶體的情況下就釋放掉記憶體
    //失去了設計共享指針的意義
    //同時,使用裸指針x本身也是很危險的,x随時可能變成空懸指針而無從知曉
               
  2. //錯誤場景2
    //函數接受一個共享指針參數
    void func(shared_ptr<int> sp);
    
    int *x(new int(10));
    //建立了一個指向x指針所指記憶體的共享指針,引用計數為1,是引用這塊記憶體的唯一共享指針
    func(shared_ptr<int> (x));
    //離開函數即離開共享指針的作用域,這塊記憶體即被删除
               
  1. 不要用p.get()的傳回值為shared_ptr指派
    shared_ptr<int> sp1(new int(10));
    //sp2與sp1獨立
    shared_ptr<int> sp2(sp1.get()),sp3;
    //sp3與sp1獨立
    sp.reset(sp1.get());
               

謹慎使用p.get()的傳回值

p.get()的傳回值就相當于一個裸指針的值,不合适的使用這個值,上述陷阱的所有錯誤都有可能發生,遵守以下幾個約定

  1. 不要儲存p.get()的傳回值 

    無論是儲存為裸指針還是shared_ptr都是錯誤的 

    儲存為裸指針不知什麼時候就會變成空懸指針 

    儲存為shared_ptr則産生了獨立指針

  2. 不要delete p.get()的傳回值 

    會導緻對一塊記憶體delete兩次的錯誤

記得向shared_ptr傳遞删除器

如果用shared_ptr管理非new對象或是沒有析構函數的類時,應當為其傳遞合适的删除器

避免形成指針循環引用

循環引用