智能指针

目录

一、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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

诸神黄昏EX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值