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對象或是沒有析構函數的類時,應當爲其傳遞合適的刪除器


避免形成指針循環引用

循環引用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章