第21課 shared_ptr共享型智能指針

一. shared_ptr的基本用法ios

(一)與unique_ptr的比較編程

比較數組

shared_ptr安全

unique_ptr併發

備註ide

初始化函數

①shared_ptr<T> sp;this

  sp.reset(new T());spa

②shared_ptr<T> sp(new T());線程

③shared_ptr<T> sp1 = sp; //拷貝構造

④auto sp = make_shared<int>(10);

①unique_ptr<T> up;

  up.reset(new T());

②unique_ptr<T> up(new T());

③unique_ptr<T> up1 = std::move(up);//移動構造

④auto up = make_unique<int>(10);

二者的構造函數將聲明爲explicit,即不容許隱式類型轉換如shared_ptr<int> sp = new int(10);

條件判斷

如,if(sp){…}

如,if(up){…}

兩都均重載operator bool()

解引用

*sp

*up

解引用,得到它所指向的對象

->mem

sp->mem

up->mem

重載->運算符

get()

sp.get()

up.get()

返回智能指針中保存的裸指針,要當心使用。

p.swap(q)

sp.swap(q);

up.swap(q);

交換p和q指針

獨有操做

①shared_ptr<T> p(q);//拷貝構造

②p = q;//賦值

③p.unique();若p.use_count()爲1,返回true,不然返回false。

④p.use_count()//返回強引用計數

①up=nullptr;釋放up指向的對象,並將up置空。

②up.release();//up放棄對指針的控制權,返回裸指針,並將up置空

③up.reset();釋放up指向的對象。

  up.reset(q);其中q爲裸指針。令up指向q所指對象。

  up.reset(nullptr);置空

注意:

①unique_ptr不可拷貝和賦值,但能夠被移動

②release會切斷unique_ptr和它原來管理的對象間的聯繫。一般用來初始化另外一個智能指針

(二)指定刪除器

  1. shared_ptr<T> sp1(q, deleter1);與unique_ptr不一樣,刪除器不是shared_ptr類型的組成部分。假設,shared_ptr<T> sp2(q,deleter2),儘管sp1和sp2有着不一樣的刪除器,但二者的類型是一致的,均可以被放入vector<shared_ptr<T>>類型的同一容器裏。

  2. 與std::unique_ptr不一樣,自定義刪除器不會改變std::shared_ptr的大小。其始終是祼指針大小的兩倍

  3. 當使用shared_ptr管理動態數組時,須要指定刪除器。由於默認刪除器不支持數組對象。如shared_ptr<int> sp(new int[10], std::default_delete<int[]>);

  4. 刪除器能夠是普通函數、函數對象和lambda表達式等。默認的刪除器爲std::default_delete,其內部是經過delete來實現功能的。

二. 剖析std::shared_ptr

(一)std::shared_ptr的內存模型

 

  1. shared_ptr包含了一個指向對象的指針和一個指向控制塊的指針。每個由std::shared_ptr管理的對象都有一個控制塊,它除了包含引用計數以外,還包含了自定義刪除器的副本和分配器的副本以及其餘附加數據

  2. 控制塊的建立規則:

  (1)std::make_shared老是建立一個控制塊

  (2)從具有全部權的指針出發構造一個std::shared_ptr時,會建立一個控制塊。(如std::unique_ptr轉爲shared_ptr時會建立控制塊,由於unique_ptr自己不使用控制塊,同時unique_ptr置空)

  (3)當std::shared_ptr構造函數使用裸指針做爲實參時,會建立一個控制塊。這意味從同一個裸指針出發來構造不止一個std::shared_ptr時會建立多重的控制塊,也意味着對象會被析構屢次。若是想從一個己經擁有控制塊的對象出發建立一個std::shared_ptr,能夠傳遞一個shared_ptr或weak_ptr而非裸指針做爲構造函數的實參,這樣則不會建立新的控制塊。

【經驗】

  ①儘量避免將裸指針傳遞給一個std::shared_ptr的構造函數,經常使用的替代手法是使用std::make_shared。

  ②若是必須將一個裸指針傳遞給shared_ptr的構造函數,就直接傳遞new運算符的結果,而非傳遞一個裸指針變量。如shared_ptr<Widget> spw (new Widget, logginDel);

  ③不要將this指針返回給shared_ptr。當但願將this指針託管給shared_ptr時,類須要繼承自std::enable_shared_from_this,而且從shared_from_this()中得到shared_ptr指針。(具體見《enable_shared_from_this》部分的分析)

  3. 引用計數(強引用計數)

  (1)shared_ptr的構造函數會使該引用計數遞增,而析構函數會使該計數遞減。但移動構造時表示從一個己有的shared_ptr移動構造到一個新的shared_ptr。這意味着一旦新的shared_ptr產生後,原有的shared_ptr會被置空,其結果是引用計數沒有變化。

  (2)複製賦值同時執行兩種操做(如sp1 和sp2是指向不一樣對象的shared_ptr,則sp1 = sp2時,將修改sp1使得其指向sp2所指的對象。而最初sp1所指向的對象的引用計數遞減,同時sp2所指向的對象引用計數遞增)

  (3)reset函數,若是不帶參數時,則引用計數減1。若是不帶參數時,如sp.reset(p)則sp原來指向的對象引用計數減1,同時sp指向新的對象(p)

  (4)若是實施一次遞減後最後的引用計數變成0,即再也不有shared_ptr指向該對象,則會被shared_ptr析構掉

  (5)引用計數的遞增和遞減是原子操做,即容許不一樣線程併發改變引用計數。

【編程實驗】shared_ptr的陷阱分析

#include <iostream>
#include <vector>
#include <memory> // for smart pointer

using namespace std;

class Widget{};

void func(shared_ptr<Widget> sp){}

int funcException() { /*throw 1;*/ return 0; } //假設該函數會拋出異常

void demo(shared_ptr<int> sp, int f){}

int main()
{
    //1. 陷阱:用同一裸指針建立多個shared_ptr
    //1.1 錯誤作法
    auto pw = new Widget;
    std::shared_ptr<Widget> spw1(pw); //強引用計數爲1,爲pw建立一個控制塊
    //std::shared_ptr<Widget> spw2(pw); //強引用計數爲1,爲pw建立另外一個新的控制塊,會致使屢次析構

    auto sp = new Widget;
    func(shared_ptr<Widget>(sp)); //慎用裸指針,sp將在func結束後被釋放!

    //1.2 正確作法
    std::shared_ptr<Widget> spw3(spw1); //ok,pw的強引用計數爲2。使用與spw1同一個控制塊。
    std::shared_ptr<Widget> spw4(new Widget); //將new的結果直接傳遞給shared_ptr
    std::shared_ptr<Widget> spw5 = std::make_shared<Widget>(); //強烈推薦的作法!
    
    //2. 陷阱:在函數實參中建立shared_ptr
    //2.1 shared_ptr與異常安全問題
    //因爲參數的計算順序因編譯器和調用約定而異。假定按以下順序計算
    //A.先前new int,而後funcException();
    //B.假設剛好此時funcException產生異常。
    //C.因異常出現shared_ptr還來不及建立,因而int內存泄露
    demo(shared_ptr<int>(new int(100)), funcException());

    //2.2 正確作法
    auto p1 = std::make_shared<int>(100);
    demo(p1, funcException());

    //3. 陷阱:shared_ptr的循環引用(應避免)(見第22課 weak_ptr)

    //4. 刪除器
    auto deleter1 = [](Widget* pw) {cout << "deleter1"<< endl; delete pw; };
    auto deleter2 = [](Widget* pw) {cout << "deleter2"<< endl; delete pw; };

    std::shared_ptr<Widget> pw1(new Widget, deleter1);
    std::shared_ptr<Widget> pw2(new Widget, deleter2);

    std::shared_ptr<Widget> pw3(pw1);
    pw3.reset(new Widget); //deleter恢復爲默認的std::default_delete

    vector<std::shared_ptr<Widget>> vecs;
    vecs.emplace_back(pw1);
    vecs.emplace_back(pw2); //pw1和pw2雖然有不一樣的刪除器,但類型相同,能夠放入同一容器內。

    //5. 其它
    //5.1 shared_ptr的大小
    cout << sizeof(spw1) << endl;//8
    cout << sizeof(pw1) << endl; //8
    //5.2 shared_ptr管理動態數組(建議用std::array、std::vector取代)
    std::shared_ptr<int> pArray1(new int[10], [](int* p) {delete[] p; }); //使用delete[]
    std::shared_ptr<int> pArray2(new int[10], std::default_delete<int[]>()); //使用default_delete<int[]>()
    //5.3 常見操做
    cout << pw1.use_count() << endl; //2

    if (pw1) //pw1.use_count >= 1 ?
    {
        cout << "pw1.use_count >= 1" << endl;
    }
    else
    {
        cout << "pw1.use_count == 0" << endl;
    }
    //5.4 別名構造
    int* p = new int(10);

    std::shared_ptr<int> a(new int(20));
    std::shared_ptr<int> b(a, p);  // alias constructor: co-owns a, points to p。可用於多繼承中
                                   // a 和 b擁用相同的控制塊,但二者指向的對象不一樣。因爲二者擁用相同的
                                   //的控制塊,可認爲a和b所指對象具備相同的擁有者,所以10和20兩個堆對象
                                   //擁有相同的生命期
    cout << *a << endl; //20
    cout << *b << endl; //10

    return 0;
}

四. enable_shared_from_this模板的分析

(一)模板分析(以boost::enable_shared_from_this爲例)

template<class T> class enable_shared_from_this
{
protected:

    enable_shared_from_this() BOOST_NOEXCEPT
    {
    }

    enable_shared_from_this(enable_shared_from_this const &) BOOST_NOEXCEPT
    {
    }

    enable_shared_from_this & operator=(enable_shared_from_this const &) BOOST_NOEXCEPT
    {
        return *this;
    }

    ~enable_shared_from_this() BOOST_NOEXCEPT // ~weak_ptr<T> newer throws, so this call also must not throw
    {
    }

public:

    shared_ptr<T> shared_from_this()
    {
        shared_ptr<T> p( weak_this_ );
        BOOST_ASSERT( p.get() == this );
        return p;
    }

    shared_ptr<T const> shared_from_this() const
    {
        shared_ptr<T const> p( weak_this_ );
        BOOST_ASSERT( p.get() == this );
        return p;
    }

public: // actually private, but avoids compiler template friendship issues

    // Note: invoked automatically by shared_ptr; do not call
    template<class X, class Y> void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const
    {
        if( weak_this_.expired() )
        {
            weak_this_ = shared_ptr<T>( *ppx, py );
        }
    }

private:

    mutable weak_ptr<T> weak_this_;
};
boost::enable_shared_from_this

     1. enable_shared_from_this模板類提供兩個public屬性的shared_from_this成員函數。這兩個函數內部會經過weak_this_(weak_ptr類型)成員來建立shared_ptr。

     2. _internal_accept_owner函數不能手動調用,這個函數會被shared_ptr自動調用,該函數是用來初始化惟一的成員變量weak_this_

     3. 根據對象生成順序,先初始化基類enable_shared_from_this,再初始化派生類對象自己。這時對象己經生成,但weak_this_成員還未被初始化,最後應經過shared_ptr<T> sp(new T())等方式調用shared_ptr構造函數(內部會調用_internal_accept_owner)來初始化weak_this_成員。而若是在調用shared_from_this函數以前weak_this_成員未被初始化,則會經過ASSERT報錯提示。

(二)使用說明

     1. 基類必須爲enable_shared_from_this<T>,其中T爲派生類的類名。(這種方法叫奇妙遞歸模板模式)

     2. 經過調用shared_from_this()成員函數得到一個和this指針指向相同對象的shared_ptr。

     3. 從內部實現看,shared_from_this會查詢當前對象的控制塊,並建立一個指向該控制塊的新shared_ptr。這樣的設計就要求當前對象己有一個與其關聯的控制塊。爲了實現這一點,就必須有一個己經存在指向當前對象的std::shared_ptr,若是不存在,則一般shared_from_this會拋出異常。

【編程實驗】安全地從this指針建立shared_ptr

#include <iostream>
#include <vector>
#include <memory>

using namespace std;

//1. 從this指針建立shared_ptr
//1.1 錯誤的作法
class Test1
{
public:
    //析構函數
    ~Test1() { cout <<"~Test1()" << endl; }

    //獲取指向當前對象的指針
    std::shared_ptr<Test1> getObject()
    {
        shared_ptr<Test1> pTest(this); //危險! 直接從this指針建立,會爲this對象建立新的控制塊!
                                       //從而可能致使this所指對象被屢次析構
        return pTest;
    }
};

//1.2 正確的作法
class Test2 : public std::enable_shared_from_this<Test2> //繼承! 注意Test2爲基類的模板參數  (遞歸模板模式)
{
public:
    //析構函數
    ~Test2() { cout << "~Test2()" << endl; }

    std::shared_ptr<Test2> getObject()
    {
        return shared_from_this(); //調用enable_shared_from_this模板的成員函數,獲取this對象的shared_ptr
    }
};

//2. shared_from_this函數的正確調用
//2.1 通常作法
class Test3 : public std::enable_shared_from_this<Test3>
{
public:
    //構造函數中不能使用shared_from_this
    Test3()
    {
        //std::shared_ptr<Test3> sp = shared_from_this(); //error,此時基類(enable_shared_from_this<Test3>)
                                                          //雖己構造完,但shared_ptr的構造函數還沒被調用,weak_this_指針
                                                          //未被初始化,所以調用shared_from_this會拋出異常
    }

    //調用process以前,必須確保shared_ptr的構造函數己被執行(即weak_this_被初始化)
    void process()
    {
        std::shared_ptr<Test3> sp = shared_from_this();
    }
};

//2.2 改進作法:利用工廠函數來提供shared_ptr
class Test4 : public std::enable_shared_from_this<Test4>
{
    Test4() {}  //構造函數設爲private
public:

    //提供工廠函數
    template<typename... Ts>
    static std::shared_ptr<Test4> create(Ts&& ... params)
    {
        std::shared_ptr<Test4> ret(new Test4(params...));
        return ret;
    }

    void process()
    {
        std::shared_ptr<Test4> sp = shared_from_this();
    }
};

//3. enable_shared_from_this的應用舉例
class Widget;
std::vector<std::shared_ptr<Widget>> processWidgets; //記錄己被處理過的Widgets

class Widget : public std::enable_shared_from_this<Widget> //須要從這裏繼承
{
public:
    void process()
    {
        //錯誤作法:直接將this傳給shared_ptr<Widget>
        //processWidgets.emplace_back(this); //將處理完的Widget加入鏈表。
                                             //error,這種作法本質上是用裸指針來建立shared_ptr,會爲this對象建立
                                             //新的控制塊。若是外部new Widget時,也將指針交給shared_ptr管理時,會出現爲同
                                             //一個this對象建立多個控制塊,從而形成this對象的屢次析構!

        //正確作法:(爲了確保shared_from_this在shared_ptr構造函數後被調用,能夠採用工廠函數的方式來建立Widget,
        //具體見前面的例子)
        processWidgets.emplace_back(shared_from_this()); //將指向當前對象的shared_ptr加入到鏈表中
    }

    ~Widget() { cout <<"~Widget()" << endl; }
};

int main()
{
    //1.  從this指針建立shared_ptr
    //1.1 錯誤作法:對象被屢次析構
    {
        //std::shared_ptr<Test1> pt1(new Test1());
        //std::shared_ptr<Test1> pt2 = pt1->getObject();
    }

    //1.2 正確作法
    {
        std::shared_ptr<Test2> pt1(new Test2());
        std::shared_ptr<Test2> pt2 = pt1->getObject();
    }

    //2. shared_from_this的正確調用
    {
        //2.1 錯誤方法:
        Test3 t;
        //t.process(); //錯誤,shared_ptr構造函數沒有被執行

        Test3* pt = new Test3();
        //pt->process(); //錯誤,緣由同上。
        delete pt;

        //正確作法
        std::shared_ptr<Test3> spt(new Test3); //shared_ptr構造被執行,weak_this_被正確初始化
        spt->process(); 

        //2.2 工廠方法提供shared_ptr,確保shared_ptr構造函數被執行!
        std::shared_ptr<Test4> spt2 = Test4::create();
        spt2->process();
    }

    //3. enable_shared_from_this的應用舉例
    {
        std::shared_ptr<Widget> sp(new Widget);
        sp->process();
    }

    return 0;
}
相關文章
相關標籤/搜索