天天看點

智能指針一、Java四種引用二、Android native智能指針三、C++四種智能指針

目錄

一、Java四種引用

二、Android native智能指針

1、LightRefBase

2、RefBase

3、sp

4、wp

5、應用場景

5.1、sp與wp的應用

5.2、線程

三、C++四種智能指針

1、auto_ptr

2、unique_ptr

3、shared_ptr

4、weak_ptr

一、Java四種引用

二、Android native智能指針

C++作為最複雜的語言除了因為文法複雜之外,還展現在指針使用不當,輕則造成記憶體洩漏,重則造成莫名其妙的邏輯錯誤或者段錯誤。是以Android系統仿照了Java的垃圾回收器實作了智能指針技術來解決這個問題。

跟其他語言的回收機制原理類似,通常通過引用計數來維護對象的生命周期。每當一個新的指針指向了一個對象時,這個對象的引用計數就增加1,相反每當一個指針不再指向一個對象時,這個對象的引用計數就減少1,當引用計數為0的時候,就安全的釋放它。

但是這種方式存在互相引用導緻無法被釋放的缺陷,是以就誕生了稍微複雜的強弱引用計數方案。即将關聯的對象劃分為父-子和子-父關系。在父-子關系中,父對象通過強引用子對象。在子-父關系中,子對象通過弱引用計數來引用父對象。規定對象生命周期不受弱引用影響,是以在互相引用的關系中就能解決這種問題。

Android系統基于上面原理,提供了三種類型的智能指針:輕量級指針、強指針、弱指針。無論是那種指針實作原理都累死,即需要對象提供引用計數器,但是由智能指針來負責維護這個引用計數器。

1、LightRefBase

LightRefBase的定義如下,其結構比較簡單是一個模闆類,内部通過成員變量mCount來對引用進行計數,incStrong方法對mCount加1,decStrong方法對mCount減1。如果需要實作輕量級指針那麼就需要繼承類LightRefBase。

template <class T>
class LightRefBase
{
public:
    // 構造函數初始化引用計數為0
    inline LightRefBase() : mCount(0) { }
    // 引用計數變量加1
    inline void incStrong(__attribute__((unused)) const void* id) const {
        mCount.fetch_add(1, std::memory_order_relaxed);
    }
    // 引用計數減1
    inline void decStrong(__attribute__((unused)) const void* id) const {
        if (mCount.fetch_sub(1, std::memory_order_release) == 1) {
            std::atomic_thread_fence(std::memory_order_acquire);
            // 當引用計數器減到0的時候通過del釋放原本對象
            delete static_cast<const T*>(this);
        }
    }
    // 擷取引用計數值
    inline int32_t getStrongCount() const {
        return mCount.load(std::memory_order_relaxed);
    }
    typedef LightRefBase<T> basetype;
protected:
    inline ~LightRefBase() { }
private:
    // 引用計數成員變量
    mutable std::atomic<int32_t> mCount;
};
           

2、RefBase

RefBase與LightRefBase類一樣,也提供了incStrong和decStrong來維護它所引用的對象的計數。但是RefBase要複雜的多,它不是直接使用一個整數來維護引用計數,而是使用weakref_type類的對象來描述對象的引用計數,該類實作類為weakref_impl還實作了強引用計數和弱引用計數的功能。是以如果需要使用強指針或者弱指針那麼就需要繼承類RefBase。

//http://androidxref.com/9.0.0_r3/xref/system/core/include/utils/RefBase.h
class RefBase
{
public:
    // 引用計數加1
    void incStrong(const void* id) const;
    // 引用計數減1
    void decStrong(const void* id) const;
    // 擷取引用計數值
    int32_t getStrongCount() const;
    // RefBase使用weakref_type來實作強引用計數和弱引用計數
    class weakref_type
    { //省略 };
    weakref_type* createWeak(const void* id) const;
    weakref_type* getWeakRefs() const;
    typedef RefBase basetype;
protected:
    RefBase();
    virtual ~RefBase();
    enum {
        OBJECT_LIFETIME_STRONG  = 0x0000, // 對象生命周期隻受強引用計數影響
        OBJECT_LIFETIME_WEAK    = 0x0001, // 對象生命周期除受強引用計數影響還受弱引用計數影響
        OBJECT_LIFETIME_MASK    = 0x0001
    };
    // 第一次引用的時候調用
    virtual void onFirstRef();
    // 最後一次強引用的時候調用
    virtual void onLastStrongRef(const void* id);
    // OBJECT_LIFETIME_WEAK模式下傳回true表示可以提升為強引用
    virtual bool onIncStrongAttempted(uint32_t flags, const void* id);
    // 最後一次弱引用的時候調用
    virtual void onLastWeakRef(const void* id);
private:
    friend class weakref_type;
    class weakref_impl;
    RefBase(const RefBase& o);
    RefBase& operator=(const RefBase& o);
private:
    weakref_impl* const mRefs;
};
void RefBase::incStrong(const void* id) const
{
    weakref_impl* const refs = mRefs;
    // 弱引用計數器加1 通過weakref_impl的incWeak函數
    refs->incWeak(id);
    // 強引用計數器加1
    refs->addStrongRef(id);
    const int32_t c = refs->mStrong.fetch_add(1, std::memory_order_relaxed);
    // 如果不是第一次強引用就return
    if (c != INITIAL_STRONG_VALUE)  {
        return;
    }
    int32_t old __unused = refs->mStrong.fetch_sub(INITIAL_STRONG_VALUE,std::memory_order_relaxed);
    // 第一次強引用回調onFirstRef函數
    refs->mBase->onFirstRef();
}
void RefBase::decStrong(const void* id) const
{
    weakref_impl* const refs = mRefs;
    // 強引用計數器減1
    refs->removeStrongRef(id);
    const int32_t c = refs->mStrong.fetch_sub(1, std::memory_order_release);
    if (c == 1) {
        std::atomic_thread_fence(std::memory_order_acquire);
        // 強引用計數器減為0的時回調onLastStrongRef方法
        refs->mBase->onLastStrongRef(id);
        int32_t flags = refs->mFlags.load(std::memory_order_relaxed);
        if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
            // OBJECT_LIFETIME_STRONG模式(生命周期隻受強引用計數)就銷毀對象
            delete this;
        }
    }
    // 弱引用計數器減1 通過weakref_impl的decWeak函數
    refs->decWeak(id);
}
           

由上面邏輯發現,RefBase與LightRefBase邏輯差不多,都提供了incStrong方法和decStrong方法,分别用來對引用計數器進行加1減1的操作,在decStrong方法中,如果引用計數器減到0的時候就負責通過delete 語句銷毀對象。他們最大差別就LightRefBase簡單的用整形成員變量mCount來表示引用計數器,RefBase使用了一個weakref_impl類型的對象mRefs來表示引用計數器。

為什麼RefBase要搞一個weakref_impl類型來表示引用計數器呢,我在學習智能指針的時候第一個思考的問題,如果我來設計的話我會定義一個強引用類型StrongRefBase,然後定義一個弱引用類型WeakRefBase,并且他們都隻用一個整形成員變量mCount來表示引用計數器,這樣設計其實有個缺陷弱指針不能提升為強指針。weakref_impl則同時實作了強引用計數和弱引用計數,并支援兩者互換。

//http://androidxref.com/9.0.0_r3/xref/system/core/libutils/RefBase.cpp
class weakref_type
{
public:
    RefBase* refBase() const;
    void incWeak(const void* id);
    void decWeak(const void* id);
    bool attemptIncStrong(const void* id);
    bool attemptIncWeak(const void* id);
    int32_t getWeakCount() const;
    void printRefs() const;
    void trackMe(bool enable, bool retain);
};
class RefBase::weakref_impl : public RefBase::weakref_type
{
public:
    std::atomic<int32_t>    mStrong; // 強引用計數器
    std::atomic<int32_t>    mWeak;   // 弱引用計數器     
    RefBase* const          mBase;   // 執行對象本身
    std::atomic<int32_t>    mFlags;  // 标志
    // 構造函數 初始化強引用計數器和弱引用計數器
    explicit weakref_impl(RefBase* base): mStrong(INITIAL_STRONG_VALUE), mWeak(0), mBase(base), mFlags(0) { }
    void addStrongRef(const void* /*id*/) { }
    void removeStrongRef(const void* /*id*/) { }
    void renameStrongRefId(const void* /*old_id*/, const void* /*new_id*/) { }
    void addWeakRef(const void* /*id*/) { }
    void removeWeakRef(const void* /*id*/) { }
    void renameWeakRefId(const void* /*old_id*/, const void* /*new_id*/) { }
    void printRefs() const { }
    void trackMe(bool, bool) { }
};
RefBase* RefBase::weakref_type::refBase() const
{
    return static_cast<const weakref_impl*>(this)->mBase;
}
void RefBase::weakref_type::incWeak(const void* id)
{
    weakref_impl* const impl = static_cast<weakref_impl*>(this);
    // 弱引用計數器加1
    impl->addWeakRef(id);
    const int32_t c __unused = impl->mWeak.fetch_add(1, std::memory_order_relaxed);
}
void RefBase::weakref_type::decWeak(const void* id)
{
    weakref_impl* const impl = static_cast<weakref_impl*>(this);
    // 弱引用計數器減1
    impl->removeWeakRef(id);
    const int32_t c = impl->mWeak.fetch_sub(1, std::memory_order_release);
    if (c != 1) return;
    atomic_thread_fence(std::memory_order_acquire);
    int32_t flags = impl->mFlags.load(std::memory_order_relaxed);
    // 弱引用計數器減到0的時候也需要釋放對象
    if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
        // OBJECT_LIFETIME_STRONG模式(對象生命周期隻受強引用計數影響)
        if (impl->mStrong.load(std::memory_order_relaxed) == INITIAL_STRONG_VALUE) {
        } else {
            delete impl;
        }
    } else {
        // 其他模式(對象生命周期同時受強引用和弱引用計數器影響) 回調onLastWeakRef
        impl->mBase->onLastWeakRef(id);
        delete impl->mBase;
    }
}
// 弱指針轉換為強指針的時候調用該函數試圖增加目标對象的強引用計數,但是可能會增加失敗,因為目标對象可能已經被釋放了,或者該目标對象不允許使用強指針引用它
bool RefBase::weakref_type::attemptIncStrong(const void* id)
{
    incWeak(id);
    weakref_impl* const impl = static_cast<weakref_impl*>(this);
    int32_t curCount = impl->mStrong.load(std::memory_order_relaxed);
    while (curCount > 0 && curCount != INITIAL_STRONG_VALUE) {
        if (impl->mStrong.compare_exchange_weak(curCount, curCount+1, std::memory_order_relaxed))  break;
    }
    if (curCount <= 0 || curCount == INITIAL_STRONG_VALUE) {
        int32_t flags = impl->mFlags.load(std::memory_order_relaxed);
        if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
            if (curCount <= 0) {
                decWeak(id);
                return false;
            }
            while (curCount > 0) {
                if (impl->mStrong.compare_exchange_weak(curCount, curCount+1, std::memory_order_relaxed)) break;
            }
            if (curCount <= 0) {
                decWeak(id);
                return false;
            }
        } else {
            if (!impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id)) {
                decWeak(id);
                return false;
            }
            curCount = impl->mStrong.fetch_add(1, std::memory_order_relaxed);
            if (curCount != 0 && curCount != INITIAL_STRONG_VALUE) {
                impl->mBase->onLastStrongRef(id);
            }
        }
    }
    impl->addStrongRef(id);
    if (curCount == INITIAL_STRONG_VALUE) {
        impl->mStrong.fetch_sub(INITIAL_STRONG_VALUE, std::memory_order_relaxed);
    }
    return true;
}
           

3、sp

sp是輕量級指針的實作類,同時也是強指針的實作類。因為輕量級指針LightRefBase和強指針RefBase都提供了方法incStrong和decStrong方法來對引用計數器進行加1或者減1,是以在sp的構造函數中通過incStrong函數進行引用計數器加1,在析構函數中通過decStrong函數進行引用計數器減1。這裡其實用了一個多态設計思想。

//http://androidxref.com/9.0.0_r3/xref/system/core/include/utils/StrongPointer.h
template<typename T>
class sp {
public:
    // 構造函數
    inline sp() : m_ptr(0) { }
    sp(T* other);
    sp(const sp<T>& other);
    sp(sp<T>&& other);
    template<typename U> sp(U* other);
    template<typename U> sp(const sp<U>& other);
    template<typename U> sp(sp<U>&& other);
    // 析構函數
    ~sp();
private:
    template<typename Y> friend class sp;
    template<typename Y> friend class wp;
    // 對象本身引用
    T* m_ptr;
};
template<typename T>
sp<T>::sp(T* other) : m_ptr(other) {
    // 構造函數中調用了對象的incStrong函數,指派m_ptr
    // 如果other繼承了LightRefBase,則該函數将其成員變量mCount進行加1
    // 如果other繼承了RefBase,則該函數将weakref_impl類型的成員變量mRefs分别對其強引用計數器mStrong和弱引用計數器mWeak加1
    if (other) other->incStrong(this);
}
template<typename T>
sp<T>::~sp() {
    // 析構函數中調用了對象的decStrong函數,m_ptr在構造函數中将other指派了它
    // 如果m_ptr繼承了LightRefBase,則該函數将其成員變量mCount進行減1
    // 如果m_ptr繼承了RefBase,則該函數将weakref_impl類型的成員變量mRefs分别對其強引用計數器mStrong和弱引用計數器mWeak減1
    if (m_ptr) m_ptr->decStrong(this);
}
           

4、wp

wp是弱指針的實作類。因為RefBase除了提供強引用計數之外,還實作了弱引用計數。是以使用sp可以實作對RefBase子類進行智能回收之外,還可以使用wp實作對RefBase子類進行智能處理。

wp類也是一個模闆類,其中模闆T表示對象實際類型,必須是RefBase子類。與強指針實作模闆類sp一樣,wp也有一個成員變量m_ptr用來執行它引用的對象,但是wp還有一個weakref_type指針類型的成員變量m_refs,用來維護對象的弱引用計數。

//http://androidxref.com/9.0.0_r3/xref/system/core/include/utils/RefBase.h
template <typename T>
class wp
{
public:
    typedef typename RefBase::weakref_type weakref_type;
    // 構造函數
    inline wp() : m_ptr(0) { }
    wp(T* other);  // NOLINT(implicit)
    wp(const wp<T>& other);
    explicit wp(const sp<T>& other);
    template<typename U> wp(U* other);  // NOLINT(implicit)
    template<typename U> wp(const sp<U>& other);  // NOLINT(implicit)
    template<typename U> wp(const wp<U>& other);  // NOLINT(implicit)
    // 析構函數
    ~wp();
    // 将弱指針更新為強指針
    sp<T> promote() const;
private:
    template<typename Y> friend class sp;
    template<typename Y> friend class wp;
    T*              m_ptr;
    weakref_type*   m_refs;
};
template<typename T>
wp<T>::wp(T* other) : m_ptr(other)
{
    // 大多數情況下構造函數實際上是調用RefBase的createWeak來對弱引用計數器加1
    if (other) m_refs = other->createWeak(this);
}
// RefBase的createWeak函數其實是将自己的弱引用計數器加1後并傳回了上面講的weakref_type類型mRefs
RefBase::weakref_type* RefBase::createWeak(const void* id) const
{
    // 弱引用計數器加1,通過weakref_impl的incWeak函數
    mRefs->incWeak(id);
    // 傳回RefBase的強弱引用計數器,即weakref_impl類型的mRefs
    return mRefs;
}
template<typename T>
wp<T>::~wp()
{
    // 析構函數是調用構造函數中傳回的mRefs對象的decWeak函數來對弱引用計數器減1
    if (m_ptr) m_refs->decWeak(this);
}
           

從上面的wp的構造函數和析構函數可以發現,wp的原理其實跟sp類似,在構造函數中通過對RefBase的weakref_impl類型引用計數器進行弱引用計數加1,在析構函數中通過對RefBase的weakref_impl類型計數器進行弱引用計數減1。

弱指針與強指針有一個很大的差別就是弱指針不可以直接操作它所引用的對象,因為它所引用的對象可能是不受弱引用計數控制,即可能引用的對象是一個無效的對象。是以在需要操作它引用的對象之前需要将這個弱指針更新為強指針,如果更新成功表示該對象還沒有被銷毀否則就不能使用。wp并不能直接操作它引用的對象,是因為wp類并沒有重載*和->操作符号,是以無法對wp通過*和->進行調用。那麼在需要操作它所引用的對象的時候,需要使用wp的成員函數promote,其實作如下:

template<typename T>
// 弱指針進行更新為強指針
// promote傳回sp類型就可以對其進行操作
sp<T> wp<T>::promote() const
{
    sp<T> result;
    // 實際上是通過weakref_impl的attemptIncStrong函數來檢查該對象是否被釋放
    if (m_ptr && m_refs->attemptIncStrong(&result)) {
        // 如果該對象有效,将m_ptr指派到上面定義的sp類中
        result.set_pointer(m_ptr);
    }
    // 傳回一個強引用,它指向的對象與該弱引用指向的對象是同一個對象
    return result;
}
           

5、應用場景

5.1、sp與wp的應用

強指針實作模闆類sp,與一般意義的智能指針概念相同,通過引用計數來記錄有多少使用者在使用一個對象,如果所有使用者都放棄了對該對象的引用,則該對象将被自動銷毀。

弱指針實作模闆類wp,也指向一個對象但是弱指針僅僅記錄該對象的位址,不能通過弱指針來通路該對象,也就是說不能通過wp來調用對象的成員函數或通路對象的成員變量。要想通路弱指針所指向的對象,需首先将弱指針更新為強指針(通過wp類所提供的promote方法)。弱指針所指向的對象是有可能在其它地方被銷毀的,如果對象已經被銷毀,wp的promote()方法将傳回空指針,這樣就能避免出現位址通路錯的情況。

class MyClass : public RefBase
{
public:
    MyClass() { };
    virtual ~MyClass() { };
    void show() { };
}
int main()
{
    // 普通指針定義
    MyClass* nomalPtr;
    // 智能指針錯誤定義方式 sp<MyClass>* strongPtr 這實際上定義了一個指針的指針
    // 智能指針正确定義方式
    sp<MyClass> strongPtr;
    wp<MyClass> weakPtr;
    // 給普通指針指派
    nomalPtr = new MyClass();
    // 給智能指針指派錯誤方式 strongPtr = new sp<MyClass>
    // 給智能指針指派正确方式
    strongPtr = nomalPtr;
    weakPtr = new MyClass();
    // 普通指針調用成員函數
    nomalPtr->show();
    // 強指針調用成員函數
    strongPtr.show();
    // 弱指針無法直接調用成員函數
    // 弱指針調用成員函數正确方式
    sp<MyClass> tempPtr = weakPtr.promote(); // 更新為強指針,不能使用->
    tempPtr.show();
    tempPtr = NULL; // 使用完後最好銷銷毀掉
    // 銷毀普通指針
    delete nomalPtr;
    nomalPtr = NULL;
    // 銷毀智能指針 不能用delete 銷毀智能指針
    strongPtr = NULL;
    weakPtr  = NULL;
}
           

5.2、線程

android提供了一個供給native世界使用的線程類,該類繼承了RefBase,通常情況下我們可以在onFirstRef中調用run函數來啟動線程。我們可以重寫函數readyToRun函數來實作初始化操作,重寫threadLoop函數來執行線程輪訓任務,通過requestExit來請求結束線程,也可以通過join函數用來等待線程執行完成。其邏輯如下:

//http://androidxref.com/9.0.0_r3/xref/system/core/libutils/include/utils/Thread.h
class Thread : virtual public RefBase
{
public:
    explicit Thread(bool canCallJava = true);
    virtual  ~Thread();
    virtual status_t    run( const char* name, int32_t priority = PRIORITY_DEFAULT, size_t stack = 0);
    // 異步請求線程退出 在函數執行後線程可能還會運作一段時間,其他線程也可以調用它
    virtual void        requestExit();
    // 線程初始化函數
    virtual status_t    readyToRun();
    // 如果該線程正在運作,那麼阻塞等待線程執行完成,如果該線程還沒有啟動,那麼立即傳回,注意不要線上程内部調用該函數,否則傳回would塊
    status_t            join();
    // 該線程是否正在運作
    bool                isRunning() const;

protected:
    // 如果調用了requestExit,該函數将傳回true
    bool exitPending() const;
private:
    // 傳回false則該線程将在傳回時退出
    // 傳回true則該線程将再次調用該函數,除非requestExit被調用過
    virtual bool threadLoop() = 0;
private:
    Thread& operator=(const Thread&);
    static  int             _threadLoop(void* user);
    const   bool            mCanCallJava;
            thread_id_t     mThread;
    mutable Mutex           mLock;
            Condition       mThreadExitedCondition;
            status_t        mStatus;
    volatile bool           mExitPending;
    volatile bool           mRunning;
            sp<Thread>      mHoldSelf;
};
// http://androidxref.com/9.0.0_r3/xref/system/core/libutils/Threads.cpp
status_t Thread::run(const char* name, int32_t priority, size_t stack)
{
    Mutex::Autolock _l(mLock);
    if (mRunning) {
        return INVALID_OPERATION;
    }
    mStatus = NO_ERROR;
    mExitPending = false;
    mThread = thread_id_t(-1);
    mHoldSelf = this;
    mRunning = true;
    bool res;
    // android平台上建立線程
    if (mCanCallJava) {
        res = createThreadEtc(_threadLoop, this, name, priority, stack, &mThread);
    } else {
        res = androidCreateRawThreadEtc(_threadLoop, this, name, priority, stack, &mThread);
    }
    if (res == false) {
        mStatus = UNKNOWN_ERROR;   // something happened!
        mRunning = false;
        mThread = thread_id_t(-1);
        mHoldSelf.clear();  // "this" may have gone away after this.
        return UNKNOWN_ERROR;
    }
    return NO_ERROR;
}
int androidCreateRawThreadEtc(android_thread_func_t entryFunction,
                               void *userData,
                               const char* threadName __android_unused,
                               int32_t threadPriority,
                               size_t threadStackSize,
                               android_thread_id_t *threadId)
{
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    if (threadStackSize) {
        pthread_attr_setstacksize(&attr, threadStackSize);
    }
    errno = 0;
    pthread_t thread;
    // android平台還是通過pthread_create建立線程
    int result = pthread_create(&thread, &attr, (android_pthread_entry)entryFunction, userData);
    pthread_attr_destroy(&attr);
    if (result != 0) {
        return 0;
    }
    if (threadId != NULL) {
        *threadId = (android_thread_id_t)thread; // XXX: this is not portable
    }
    return 1;
}
// 線程被啟動後會執行該函數進行具體的邏輯實作
int Thread::_threadLoop(void* user)
{
    Thread* const self = static_cast<Thread*>(user);
    sp<Thread> strong(self->mHoldSelf);
    wp<Thread> weak(strong);
    self->mHoldSelf.clear();
    bool first = true;
    do {
        bool result;
        if (first) {
            first = false;
            // 第一次輪訓回調readyToRun方法 可以對線程進行初始化工作
            self->mStatus = self->readyToRun();
            result = (self->mStatus == NO_ERROR);
            if (result && !self->exitPending()) {
                // 第一次輪訓回調threadLoop方法 可以對線程進行任務工作
                result = self->threadLoop();
            }
        } else {
            // 輪訓回調threadLoop方法 可以對線程進行任務工作
            result = self->threadLoop();
        }
        {
        Mutex::Autolock _l(self->mLock);
        // threadLoop方法傳回false将終止線程
        // mExitPending為true也将終止線程 該變量在requestExit中被置為true
        if (result == false || self->mExitPending) {
            self->mExitPending = true;
            self->mRunning = false;
            self->mThread = thread_id_t(-1);
            self->mThreadExitedCondition.broadcast();
            break;
        }
        }
        strong.clear();
        strong = weak.promote();
    } while(strong != 0);
    return 0;
}
void Thread::requestExit()
{
    Mutex::Autolock _l(mLock);
    mExitPending = true;
}
bool Thread::isRunning() const {
    Mutex::Autolock _l(mLock);
    return mRunning;
}
status_t Thread::join()
{
    Mutex::Autolock _l(mLock);
    if (mThread == getThreadId()) {
        return WOULD_BLOCK;
    }
    // 線程正在運作狀态将挂在這個死循環中
    while (mRunning == true) {
        mThreadExitedCondition.wait(mLock);
    }
    return mStatus;
}
           

三、C++四種智能指針

自2008年Android系統問世以來,Android系統内部使用了大量的SP這些玩意,其實這個時候C++的設計者對自己管理記憶體過于複雜的缺陷深知杜明,早在C++98版本(1998年釋出)就已經引入了智能指針auto_ptr,但因為一系列缺陷直到C++11版本(2011年釋出)才引入了unique_ptr和shared_ptr智能指針。這樣也可以說通為什麼Android系統甯願自己重新設計智能指針也不适應98版本的auto_ptr。

1、auto_ptr

類的構造和析構函數(釋放資源)是由編譯器自動調用的,基于該原理引入了智能指針的概念(即希望指針跟類一樣在建立後能夠像析構函數一樣自動被編譯器釋放)。是以引入了最初的auto_ptr,其原理定義一個類來封裝資源的配置設定和釋放,在構造函數完成資源的配置設定和初始化,在析構函數完成資源的清理,可以保證資源的正确初始化和釋放。其實作如下:

namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
template<typename _Tp1>
struct auto_ptr_ref {
    _Tp1* _M_ptr; 
    explicit auto_ptr_ref(_Tp1* __p): _M_ptr(__p) { }
} _GLIBCXX_DEPRECATED;
template<typename _Tp>
class auto_ptr
{
private:
    _Tp* _M_ptr;
public:
    typedef _Tp element_type;
    //構造函數:預設為nullptr
    //auto_ptr<T> x1( new T())
    explicit
    auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { }
    //拷貝構造函數: 從相同類型的智能指針x1中拷貝到新建立的x2中
    //auto_ptr<T> x2(x1)
    auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) { }
    //拷貝構造函數:從不同類型的智能指針中拷貝到新建立的x3中
    //auto_ptr<T> x3(new _Tp1()) 這裡講_Tp1強制轉換成T
    template<typename _Tp1>
    auto_ptr(auto_ptr<_Tp1>& __a) throw() : _M_ptr(__a.release()) { }
    //重載=運算符:将相同類型的智能指針__a進行指派
    auto_ptr& operator=(auto_ptr& __a) throw() {
	    reset(__a.release());
	    return *this;
    }
    //重載=運算符:将不同類型_Tp1的指針__a進行指派 相當于強制轉換成T 被棄用
    template<typename _Tp1>
    auto_ptr& operator=(auto_ptr<_Tp1>& __a) throw() {
	    reset(__a.release());
	    return *this;
	}
    //重*運算符:重載函數内部作了*解引用
    element_type& operator*() const throw() {
	    _GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
	    return *_M_ptr;
    }
    //重載->運算符:重載函數傳回了原始指針,對其->就相當于對原始指針->
    element_type* operator->() const throw() {
	    _GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
	    return _M_ptr; 
    }
    //get函數:傳回原始指針
    element_type* get() const throw() { return _M_ptr; }
    //release函數:除了傳回原始指針之外還進行了權限轉移
    element_type* release() throw() {
	    element_type* __tmp = _M_ptr;
	    _M_ptr = 0;  //進行權限轉移,将自己的原始指針置NULL,即調用該函數後自己變成了空引用
	    return __tmp;
    }
    //reset函數:重置自己引用的原始指針,跟構造函數一樣預設nullptr
    void reset(element_type* __p = 0) throw() {
	    if (__p != _M_ptr) {
	        delete _M_ptr; //如果重置的指針跟自己以前不一樣,就自動釋放以前的原始指針
	        _M_ptr = __p;  //如果__p為nullptr,也實作了清除原始指針的功能
	    }
    }
    //重載=運算符:跟reset一樣
    auto_ptr& operator=(auto_ptr_ref<element_type> __ref) throw() {
	    if (__ref._M_ptr != this->get()) {
	        delete _M_ptr;
	        _M_ptr = __ref._M_ptr;
	    }
	    return *this;
    }
    //析構函數:釋放原始指針
    ~auto_ptr() { delete _M_ptr; }
}
           

如上auto_ptr的定義可以不難看出,auto_ptr模闆類内部持有了原始指針_M_ptr,在構造函數中或者重載=函數中對其進行初始化,在析構函數中對其進行釋放,且重新定義了reset和release函數。可以歸納總結如下:

  • 建構auto_ptr:無參數建構auto_ptr,其内部原始指針是nullptr,注意這個時候能夠使用reset/get/release函數,但不能對其進行->操作,否則出現段錯誤,因為重載->函數中實際是對原始指針進行->操作。
  • 指派auto_ptr:指派運算符(=)的右邊必須也是auto_ptr類型,注意絕對不能是原始指針類型(編譯報錯因為沒有對其進行重載);重載函數中如果判斷等号左右兩邊的auto_ptr所引用的原始指針是否是同一個對象,不是就會先釋放自己的再進行指派操作。
  • reset函數:跟指派一樣可以通過ptr.reset(ptr2)重置原始指針,且可以通過ptr.reset()來對現有的原始指針進行資源釋放。但是需要提醒的,指派運算左右兩邊是auto_ptr類型,reset函數的參數需要是原始指針類型。
  • get函數:傳回持有的原始指針。
  • release函數:傳回持有的原始指針,還進行權限轉移,即調用該函數後,該auto_ptr智能指針就是空引用,對其->操作也會出現段錯誤。
  • 析構函數:跟類一樣,在生命周期結束的時候,所持有的原始指針自動釋放
  • 空引用判斷:需要注意的是auto_ptr并沒有重載==運算符,是以無法通過==來進行判斷是否為nullptr,經過驗證也無法直接使用if(auto_ptr)來判斷智能指針指向的引用是否為空。知道的朋友請告訴我一下
#include <string>
#include <memory>
using std::auto_ptr;
using std::string;
class AvStart{
public:
    AvStart(string tempName) : name(tempName) { };
    void show(){
        printf("I am %s \n", name.c_str());
    };
private:
    string name;
};
int main(){
    //建構了一個空的auto_ptr
    auto_ptr<AvStart> x0;
    //調用空引用的auto_ptr程式還是會崩潰觸發段錯誤
    x0->show();
    //建構非空auto_ptr
    auto_ptr<AvStart> x1(new AvStart("JIosn");
    x1->show();    //輸出:I am JIosn
    //拷貝構造
    auto_ptr<AvStart> x2(x1);
    x2->show();    //輸出:I am JIosn
    //拷貝構造的時候會調用x1的release函數轉移控制權
    //x1->show();  //程式崩潰觸發段錯誤 
    
    auto_ptr<AvStart> x3(new AvStart("DingPC"));
    //get擷取原始指針
    AvStart *p1 = x3.get();
    p1->show();    //輸出:I am DingPC
    x3->show();    //輸出:I am DingPC
    //release轉移權限 權限被轉移後就變成了空引用
    AvStart *p2 = x3.release();
    p2->show();    //輸出:I am DingPC
    x3->show();    //程式崩潰觸發段錯誤
    
    //reset重置指針等價于指派運算
    auto_ptr<AvStart> x4(new AvStart("CangJK");
    x4->show();    //輸出:I am CangJK
    //重置原始指針為new AvStart("SongDF", "IPZ-560")
    x4.reset(new AvStart("SongDF", "IPZ-560"));
    x4->show();    //輸出:I am SongDF
    //重置原始指針為nullptr
    x4.reset();
    x4->show();    // 程式崩潰觸發段錯誤

    //指派原始指針 編譯器報錯
    //不允許對auto_ptr指派原始指針 因為沒有重載指派原始指針函數
    auto_ptr<AvStart> x5 = new AvStart("LiuBang"); 
    
    //指派auto_ptr
    auto_ptr<AvStart> x6(new AvStart("Abigaile Johnson"));
    auto_ptr<AvStart> x7(new AvStart("Anjelica"));
    x6->show();    //輸出:I am Abigaile Johnson
    //與reset(原始指針)一樣 先釋放Abigaile Johnson再指派
    x6 = x7;
    x6->show();    //輸出:I am Anjelica
    //與reset()一樣 先釋放Anjelica再指派nullptr
    x6 = nullptr;
    //編譯器報錯,無法對其進行==操作
    if (x6 == nullptr) {}
    //編譯器報錯,無法将其轉換成bool類型
    if (x6) {}
    return 0;
}
           

雖然auto_ptr基本上已經可以稱得上智能指針了,但是因為沒有使用引用計數等先進計數,是以還是有很多缺陷,導緻android甯願自己重新設計也不願意使用auto_ptr。同時在一篇文章中有看到千萬不要使用auto_ptr,詳細參考請點選。

2、unique_ptr

C++11引入了另外兩個智能指針,宣告着auto_ptr全面被淘汰。新版本中的兩個智能指針分别是unique_ptr(獨享指針)和shared_ptr(共享指針)。下面先來分析獨享指針。

unique_ptr簡稱為獨享指針,意思就是它所引用的對象被獨自占有。跟auto_ptr一樣它也是一個模闆類,其包裝一個原始指針,并負責其生命周期。當該對象被銷毀時,會在其析構函數中删除關聯的原始指針;為了讓其能夠跟普通指針一樣,其對運算符->和

*

進行了重載。除此之外為了能夠"獨享"資源,其不允許指派和拷貝等操作。

//構造函數
//預設構造函數:預設引用的對象為nullptr
//例如:unique_ptr<T> p;建立unique_ptr對象但是并沒有管理資源
constexpr unique_ptr() : _M_t(){}
constexpr unique_ptr(nullptr_t) : unique_ptr() { }
//初始化構造函數:在沒有指定删除器的情況下使用deleter_type
//例如:unique_ptr<T> p(new T());建立unique_ptr對象且内部引用了T類型的對象
explicit unique_ptr(pointer __p) : _M_t(__p, deleter_type()) { }
//初始化構造函數:同時指定了删除器
unique_ptr(pointer __p, typename conditional<is_reference<deleter_type>::value, deleter_type, const deleter_type&>::type __d) 
    : _M_t(__p, __d) { }
unique_ptr(pointer __p,  remove_reference<deleter_type>::type&& __d)
    : _M_t(std::move(__p), std::move(__d)) {  }
//移動構造函數:參數是unique_ptr&&類型表示需要通過std::move移交所有權
//例如:unique_ptr<T> p1(std::move(p2)) 其中P2也是unique_ptr對象
unique_ptr(unique_ptr&& __u)
    : _M_t(__u.release(), std::forward<deleter_type>(__u.get_deleter())) { }
//移動構造函數:隐式轉換
template<typename _Up, typename _Ep, typename = _Require<is_convertible<typename unique_ptr<_Up, _Ep>::pointer, pointer>, __not_<is_array<_Up>>, typename conditional<is_reference<_Dp>::value, is_same<_Ep, _Dp>, is_convertible<_Ep, _Dp>>::type>>
unique_ptr(unique_ptr<_Up, _Ep>&& __u)
	: _M_t(__u.release(), std::forward<_Ep>(__u.get_deleter())) { }
//移動構造函數:同上這裡是把auto_ptr隐式轉換成unique_ptr
template<typename _Up, typename = _Require<is_convertible<_Up*, _Tp*>, is_same<_Dp, default_delete<_Tp>>>>
unique_ptr(auto_ptr<_Up>&& __u);

//析構函數:擷取所管理的删除器來釋放資源
~unique_ptr() {
    auto& __ptr = std::get<0>(_M_t);  //擷取管理資源的引用
	if (__ptr != nullptr)
	    get_deleter()(__ptr);         //用删除器進行資源釋放
	__ptr = pointer();                //重置管理指針
}
//get函數:傳回目前引用的資源對象
pointer get() const {
    return std::get<0>(_M_t);
}
//重載->運算符
pointer operator->() const {
    return get();
}
//解引用
typename add_lvalue_reference<element_type>::type operator*() const {
    return *get();
}
//重載bool函數:例如if (p) { };如果目前引用的資源不為nullptr傳回true
explicit operator bool() const {
    return get() == pointer() ? false : true;
}
//release函數:移交控制權(與auto_ptr類似)
pointer release() {
	pointer __p = get();            //先擷取目前引用的資源
	std::get<0>(_M_t) = pointer();  //将目前引用資源的指針置0,實作管理權的移交
	return __p;                     //傳回目前引用的資源
}
//reset函數:重置目前引用資源的指針,為了防止__p == this時錯誤的銷毀了該資源,用swap函數交換資源,然後用deleter删除交換給原始指針的資源
void reset(pointer __p = pointer()) {
	std::swap(std::get<0>(_M_t), __p);
	if (__p != pointer())
        get_deleter()(__p);
}
//重載=運算符:移動指派,即=号右邊是unique_ptr&&類型,與移動構造函數類似
//例如:unique_ptr<T> p1 = std::move(p2);其中p2也是unique_ptr對象
unique_ptr& operator=(unique_ptr&& __u) {
	reset(__u.release());
	get_deleter() = std::forward<deleter_type>(__u.get_deleter());
	return *this;
}
template<typename _Up, typename _Ep> typename enable_if< __and_< is_convertible<typename unique_ptr<_Up, _Ep>::pointer, pointer>, __not_<is_array<_Up>> >::value, unique_ptr&>::type operator=(unique_ptr<_Up, _Ep>&& __u) {
    reset(__u.release());
	get_deleter() = std::forward<_Ep>(__u.get_deleter());
	return *this;
}
//重載=運算符:指派nullptr,即=号右邊是nullptr,将釋放資源
unique_ptr& operator=(nullptr_t) {
	reset();     //通過reset重置管理資源的指針,因為reset沒有參數即預設為nullptr
	return *this;
}
//重載=運算符:删除unique_ptr對象之間指派,獨享資源不允許資源指派,除非通過std::move轉移權限
//例如:unique_ptr<T> p1 = p2;其中p2也是unique_ptr對象,這個時候編譯器報錯
unique_ptr& operator=(const unique_ptr&) = delete;
//拷貝構造函數:删除同類型拷貝函數,同上,獨享資源不允許拷貝,除非通過td::move轉移權限
//例如:unique_ptr<T> p1(p2);其中p2也是unique_ptr對象,這個時候編譯器報錯
unique_ptr(const unique_ptr&) = delete;

           

如上unique_ptr的定義可以不難看出,unique_ptr原理大體上其實跟auto_ptr類似。除了實作的一些細節之外,還增加了删除器的操作(一般用于對數組的管理),另外還将獨享的特性發揮的淋漓盡緻。可以歸納總結如下:

  • 建構unique_ptr:無參數建構auto_ptr,其内部原始指針是nullptr,注意這個時候能夠使用reset/get/release函數,但不能對其進行->操作,否則出現段錯誤,因為重載->函數中實際是對原始指針進行->操作。
  • 指派unique_ptr:不能對unique_ptr進行指派和拷貝,否則編譯器将報錯,但是可以通過轉移權限的方式(std::move)來進行拷貝或者指派,需要注意的被轉移權限後的unique_ptr對象将變成空引用對象,這個時候對其進行->操作将觸發段錯誤。
  • reset函數:重載unique_ptr所引用的原始指針,如果參數為nullptr或者無參數,那麼可以用來對unique_ptr進行資源釋放;如果參數為其他資源,那麼先釋放自己的原始指針然後再對其進行重置。
  • release函數:傳回持有的原始指針,且進行權限轉移,即調用該函數後,unique_ptr所引用的原始指針被置nullptr,對其->操作也會出現段錯誤。
  • get函數:傳回持有的原始指針,不進行權限轉移
  • 析構函數:跟類一樣,在生命周期結束的時候,所持有的原始指針自動釋放
  • 空引用判斷:跟auto_ptr不一樣,unique_ptr重載了==和bool運算符,即可以通過if(p1==nullptr)或if (p1)來進行非空判斷
#include <string>
#include <memory>
using std::string;
using std::unique_ptr;
using std::make_unique;
class AV{
public:
        AV(string s) : name(s) { };
        void show(){
                printf("I am %s \n", name.c_str());
        };
private:
        string name;
};
int main(){
    //有參構造
    unique_ptr<AV> p(new AV("BoDuoYeJieYi"));
    p->show();    //輸出:I am BoDuoYeJieYi
    //重置成其他資源
    p.reset(new AV("CangJinKong"));
    p->show();    //輸出:I am CangJinKong
    //重置為nullptr
    p.reset();    //等價于p = nullptr
    p->show();    //輸出:程式崩潰段錯誤
    //擷取原始指針但不轉移權限
    AV *x = p.get();
    p->show();    //輸出:I am CangJinKong
    x->show();    //輸出:I am CangJinKong
  //delete x;//禁止将智能指針的原始指針擷取出來後對其進行delete 否則智能指針持有了被銷毀的資源
    //擷取原始指針且轉移權限
    AV *y = p.release();
    y->show();    //輸出:I am CangJinKong
    p->show();    //輸出:程式崩潰段錯誤 權限已經被移除,即p所引用的原始指針是nullptr

    unique_ptr<AV> p1(new AV("XiaoDaoNan"));
    unique_ptr<AV> p2(new AV("XiangZheNan"));
    //指派操作
    p1 = std::move(p2); //正确的指派操作        
    p1 = p2;            //錯誤的指派操作 編譯器報錯
    p1->show();         //輸出:I am XiangZheNan
    p2->show();         //輸出:程式崩潰段錯誤 權限已經被移除,即std::move等價于release
    //判空操作
    if (p1)             //輸出:p1 is not NULL
        printf("p1 is not NULL");
    if (p2 == nullptr)  //輸出:p2 is NULL
        printf("p2 is NULL");
    //作為函數參數
    Unique_ptr<AV> px(new AV("FuckYou"));
    printfAV(px);       //參數指派 編譯器報錯
    return 0;
}
void printfAV(Unique_ptr<AV> av) {
    av->show();
}
           

我們已經知道了unique_ptr實作了對資源的智能管理,并且不允許進行指派和拷貝操作,這樣能夠保證指針的穩定性,在多線程開發中這樣往往更加不容易出現各種記憶體引起的異常問題。那麼現在遇到一個很棘手的問題,當調用一個函數的時候,需要傳遞unique_ptr,因為unique_ptr是獨享指針不允許指派和拷貝,那麼它還能作為形參嗎?其實有兩種方案來解決這個問題如下:

  • 函數形參使用引用:引用其實是一個變量或者對象的别名,中間不存在實參與形參之間的指派操作,是以在函數内部操作被聲明引用的變量,其實就是操作函數實參。如下例子,函數printfAV1的參數av是引用,是以av其實就是px的别名,因為av和px都是同一個變量,是以這裡不存在指派操作也不存在控制權轉移操作,調用該函數後,px還是px。是以在函數參數傳遞的情況下首推使用引用的方式,這樣比較穩定且不容易出現權限轉移類似的錯誤。
//函數參數是引用 雖然内部作了很複雜的轉換但是我們隻需要了解av是實參的别名,操作av就是操作實參
void printfAV1(unique_ptr<AV> &av) {
        av->show();   //因為av是别名,是以可以簡單的了解為av就是px,中間沒有發生指派操作
}
void main(){
        unique_ptr<AV> px(new AV("FuckYou"));
        printfAV1(px);    //輸出:I am FuckYou
        px->show();      //輸出:I am FuckYou
}
           
  • 函數實參轉移權限:除了引用我們可以正面實參與形參之間的指派操作,不就是一個指派操作嘛,從前面的知識我們很容易想到std::move能夠進行權限轉移,即能夠通過std::move的方式将unique_ptr對象内部管理的指針指派到其他的unique_ptr對象上面,需要注意的是被轉移權限後的unique_ptr對象就變成了空引用了。如下:
//函數參數不是引用 這時候需要通過std::move()進行權限轉移 否則編譯器報錯
void printfAV2(unique_ptr<AV> av){
        av->show();
}
void main(){
        unique_ptr<AV> px(new AV("FuckYou"));
        printfAV2(std::move(px));    //輸出:I am FuckYou
        //std::move(px)對px進行權限轉移後,px内部管理的指針就是nullptr
        px->show();       //程式崩潰段錯誤
}
           
  • 函數傳回unique_ptr對象:雖然unique_ptr不允許指派和拷貝操作,但是唯獨有個例外,編譯器允許将unique_ptr指派到一個即将要消亡的變量上(例如函數傳回值return);雖然我不知道這是怎麼實作的,但是這樣能夠很好的繞開函數傳回的問題。是以在函數實參轉移權限的後遺症也有了解決方案,即在函數傳回的時候再次将其傳回(調用函數的時候轉移權限,函數執行結束将權限傳回)。如下:
//函數接收unique_ptr參數,在将其傳回
unique_ptr<AV> printfAV3(unique_ptr<AV> av){
        av->show();
        return av;
}
void main(){
        unique_ptr<AV> px(new AV("FuckYou"));
        //px先通過move轉移權限給printfAV3的形參,該函數結束又将其傳回給px,px再次獲得權限
        px = printfAV3(std::move(px)); //輸出:I am FuckYou
        px->show();                    //輸出:I am FuckYou
}
           

3、shared_ptr

shared_ptr跟unique_ptr類似,他們唯一的差別就是unique_ptr不允許拷貝和指派是以被稱為獨享指針,shared_ptr内部采用了引用計數器,允許被拷貝和指派是以被稱為共享指針。如下代碼:

#include <string>
#include <memory>
using std::string;
using std::shared_ptr;
using std::make_shared;
class AV{
public:
        AV(string s) : name(s) { };
        void show(){
                printf("I am %s \n", name.c_str());
        };
private:
        string name;
};
//聲明全局變量:shared_ptr對象
shared_ptr<AV> p(new AV("Joins"));
//函數參數為shared_ptr對象
//函數調用的時候,實參指派形參av,這裡相當于一次指派,是以實參的引用計數将加1
//函數結束的時候,形參av将被釋放,av的析構函數将被調用,内部引用的資源引用計數将減1
void printfCOunt(shared_ptr<AV> av) {
    av->show();
    printf("av count is %d \n", av.use_count()); //輸出:av count is 4
    printf("p  count is %d \n", p.use_count());  //輸出:p  count is 4
}
int main(){
    p->show();
    printf("My count is %d \n", p.use_count());//輸出: My count is 1
    //指派:允許指派,p和q内部引用的是同一資源對象,且它的計數器将加1
    shared_ptr<AV> q = p;
    q->show();
    printf("My count is %d \n",q.use_count());//輸出:My count is 2
    printf("My count is %d \n",p.use_count());//輸出:My count is 2
    //拷貝:運作拷貝,c和q内部引用的是同一資源對象,且它的計數器将加1
    shared_ptr<AV> c(q);
    c->show();
    printf("My count is %d \n",c.use_count());//輸出:My count is 3
    printf("My count is %d \n",q.use_count());//輸出:My count is 3
    printf("My count is %d \n",p.use_count());//輸出:My count is 3
    //函數調用,在函數内部執行的期間指派給形參,是以p的計數器将被加1
    printfCOunt(p);
    //函數結束,因為形參局部變量被釋放,是以p的計數器将被減1
    printf("My count is %d \n",c.use_count());//輸出:My count is 3
    printf("My count is %d \n",q.use_count());//輸出:My count is 3
    printf("My count is %d \n",p.use_count());//輸出:My count is 3
    //資源釋放:指派nullptr,将内部引用對象的計數減1,且與其失去關聯,即p成為了空引用
    p = nullptr;
    printf("My count is %d \n",c.use_count());//輸出:My count is 2
    printf("My count is %d \n",q.use_count());//輸出:My count is 2
    printf("My count is %d \n",p.use_count());//輸出:My count is 0
    //資源釋放:reset(nullptr),等價于指派nullptr,同上
    c.reset();
    printf("My count is %d \n",c.use_count());//輸出:My count is 0
    printf("My count is %d \n",q.use_count());//輸出:My count is 1
    printf("My count is %d \n",p.use_count());//輸出:My count is 0
    //擷取引用的對象:并不會改變引用計數,慎用,千萬不要對get出來的對象進行delete
    AV *av = q.get();
    av->show();                               //輸出:I am Joins
    printf("My count is %d \n",q.use_count());//輸出:My count is 1
    //shared_ptr不支援release函數
    //q.release();
    return 0;
}
           

shared_ptr和unique_ptr其實還支援一種安全的方式進行構造,使用std::make_xxx函數。如下:

#include <memory>
void main() {
    //安全建構shared_ptr對象,make_shared函數内部将執行個體化一個AV對象
    std::shared_ptr<AV> sp = std::make_shared<AV>("CangLaoS");
    //安全建構unique_ptr對象,make_unique函數内部将配置設定一個AV對象
    std::unique_ptr<AV> up = std::make_unique<AV>("BoLaoS");
}
           

除此之外,shared_ptr對象可以使用unique()函數來判斷該對象内部引用的資源的use_count()是否為1。

4、weak_ptr