前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android基础--智能指针

Android基础--智能指针

作者头像
小蚂蚁与大象
发布2020-04-17 17:25:04
1.6K0
发布2020-04-17 17:25:04
举报
文章被收录于专栏:构建FFmpeg Android播放器

智能指针分为3类为轻量级指针(Light Pointer)、强指针(Strong Pointer)和弱指针(Weak Pointer)。轻量级指针采用的是简单的计数,可以认为是强指针的简化版本。在播放器的C++代码中,特别是涉及到binder通讯的地方有很多智能指针的应用,比如jni中

代码语言:javascript
复制
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);

sp是强指针的意思。

基于Android8.1 代码地址 system/core/include/utils/RefBase.h system/core/include/utils/StrongPointer.h system/core/include/libutils/RefBase.cpp

轻量级指针

指针的问题

C/C++指针问题可以归纳为以下2类:

  1. 野指针
  • 指针未初始化,当我们去判断一个指针是否可用时,往往会判断指针是否为NULL。未初始化的指针,它有可能指向了一个未知的地址。指针初始化是必须要养成的习惯。
  • 将对象delete后,未将指向它的指针设为NULL,这种情况同指针未初始化一样。
  • 另外一种情况是有多个指针指向了对象A,当某个地方将对象A delete后,操作地方地方的指针,就是对一个非法的内存进行操作
  1. new了对象后没有delete 动态分配内存是需要程序员主动去删除的,不然会造成内存泄漏。比如在一个函数中new了一个对象,并将这个对象作为返回值返回。对于一个多人维护的比较复杂工程,如果有这样的函数,并不一定所以人都会留意去释放内存,或者改对象需要被多个地方使用到,要在合适的地方去释放该对象不是那么好处理的。

解决指针问题

智能指针就是为了解决以上问题的,在了解Android 智能指针之前。先来分析下如何解决以上问题, 首先需要有一种能够自动释放的方法,而对于程序代码而言,只有栈内存才会自动去释放。C++的类,构造函数和析构函数会在创建和销毁时自动调用到。利用好这两点是实现智能指针的基础

智能指针是类

初步设计,智能指针是一个类,类有一个成员指针,能指向任意的object,所以是一个模板类

代码语言:javascript
复制
template <typename T>
class sp {
    sp()  {}
    ~sp() {}
    private:
        T* ptr;
}

对于指针未初始化 只需要在构造进行处理即可

代码语言:javascript
复制
template <typename T>
class sp {
    sp() :m_ptr(0){}
    ~sp() {}
    private:
        T* m_ptr;;
}

而delete后未置为NULL,还要结合计数问题来考虑,因为会有多个指针指向同一个地址

计数问题

智能指针如何判断对象的内存不在需要呢,在很多领域有引用计数的概念,及当没有指针指向该内存时,就可以认为该内存对象不需要了。 那么该如果计数呢,是否能由智能指针来计数? 明显是很难做到的,如下图,两个智能指针的内存空间是独立的,智能指针持有计数变量,各指针变量之间很难同步

sp.png

另一种方法是object自己计数,这需要object继承一个类

sp.png

当sp类创建时,调用incStrong方法增加计数,当sp释放时,调用decStrong方法减少计数,当mCount为0时则删除object内存. 根据使用方法

代码语言:javascript
复制
sp<MediaPlayer>(p);
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);

sp类需要重载=运算符和复制构造函数。因此sp类的定义如下

代码语言:javascript
复制
template <typename T>
class sp {
    sp() :m_ptr(0){}
    ~sp() {}

    sp(T* other); 
    sp& operator = (T* other);
    private:
        T* m_ptr;;
}

sp类在构造函数中调用incStrong增加计数,在析构函数中调用decStrong减少引用计数

代码语言:javascript
复制
template<typename T>
sp<T>::sp(T* other)
: m_ptr(other)
  {
    if (other) other->incStrong(this);
  }

template<typename T>
sp<T>& sp<T>::operator = (T* other)
{
    if (other) other->incStrong(this);
    if (m_ptr) m_ptr->decStrong(this);
    m_ptr = other;
    return *this;
}
template<typename T>
sp<T>::~sp()
{
    if (m_ptr) m_ptr->decStrong(this);
}

重载=运算符是考虑同一对象重复赋值的情况。

代码语言:javascript
复制
template <class T>
class LightRefBase
{
public:
    inline LightRefBase() : mCount(0) { }
    inline void incStrong(__attribute__((unused)) const void* id) const {
        __sync_fetch_and_add(&mCount, 1);
    }
    inline void decStrong(__attribute__((unused)) const void* id) const {
        if (__sync_fetch_and_sub(&mCount, 1) == 1) {
            delete static_cast<const T*>(this);
        }
    }
    //! DEBUGGING ONLY: Get current strong ref count.
    inline int32_t getStrongCount() const {
        return mCount;
    }

    typedef LightRefBase<T> basetype;

protected:
    inline ~LightRefBase() { }

private:
    friend class ReferenceMover;
    inline static void moveReferences(void*, void const*, size_t,
            const ReferenceConverterBase&) { }

private:
    mutable volatile int32_t mCount;
};

LightRefBase 的decStrong当引用计数为1时,会将自身delete掉。 LightRefBase是Andorid轻量级智能指针的实现方式。而MediaPlayer继承的是RefBase,会比较复杂,涉及到弱指针转强指针的问题。不过原理是一样的。

强指针

强指针 跟轻量级指针 使用的sp类是一样的,不同的是object继承的类是RefBase。看一下MediaPlayer的继承关系

代码语言:javascript
复制
class MediaPlayer : public BnMediaPlayerClient,
                    public virtual IMediaDeathNotifier
class IMediaDeathNotifier: virtual public RefBase

强指针的原理其实跟轻指针一样,都是引用计数。

代码语言:javascript
复制
class RefBase
{
public:
            void            incStrong(const void* id) const;
            void            decStrong(const void* id) const;

    class weakref_type
    {
    public:
        RefBase*            refBase() const;

        void                incWeak(const void* id);
        void                decWeak(const void* id);

    };

   weakref_type*   createWeak(const void* id) const;
   weakref_type*   getWeakRefs() const;
   typedef RefBase basetype;

protected:
                            RefBase();
    virtual                 ~RefBase();

    //! Flags for extendObjectLifetime()
    enum {
        OBJECT_LIFETIME_STRONG  = 0x0000,
        OBJECT_LIFETIME_WEAK    = 0x0001,
        OBJECT_LIFETIME_MASK    = 0x0001
    };

private:

        weakref_impl* const mRefs;
};

RefBase 嵌套了内部类weakref_type,大部分的工作其实都是weakref_type完成的。RefBase 还有一个成员变量 weakref_impl* const mRefs, 从名字看 weakref_impl 继承自weakref_type,是它的实现类。

代码语言:javascript
复制
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;

weakref_impl(RefBase* base)
        : mStrong(INITIAL_STRONG_VALUE)
        , mWeak(0)
        , mBase(base)
        , mFlags(0)
        , mStrongRefs(NULL)
        , mWeakRefs(NULL)
        , mTrackEnabled(!!DEBUG_REFS_ENABLED_BY_DEFAULT)
        , mRetain(false)
    {
    }
}

先来了解下强指针会调用到的incStrong和decStrong

代码语言:javascript
复制
void RefBase::incStrong(const void* id) const
{
    weakref_impl* const refs = mRefs;
    refs->incWeak(id);  //增加弱引用计数器   
    refs->addStrongRef(id);  //调试目的,可以不管

   //C++11 std::atomic 成员函数
   //T fetch_add (T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
   //将原子对象的封装值加 val,并返回原子对象的旧值(适用于整形和指针类型的 std::atomic 特化版本),整个过程是原子的。sync 参数指定内存序:
    const int32_t c = refs->mStrong.fetch_add(1, std::memory_order_relaxed); //强引用计数加1

    if (c != INITIAL_STRONG_VALUE)  {
        return;
    }

   //mStrong在构造函数初始化时被赋值为INITIAL_STRONG_VALUE,
   //所以第一次增加时还需要
   //减去INITIAL_STRONG_VALUE,mStrong的值才为1
    int32_t old __unused = refs->mStrong.fetch_sub(INITIAL_STRONG_VALUE, std::memory_order_relaxed);
    // A decStrong() must still happen after us.
    ALOG_ASSERT(old > INITIAL_STRONG_VALUE, "0x%x too small", old);
    refs->mBase->onFirstRef(); //RefBase为空方法,可由子类继承实现
}

void RefBase::decStrong(const void* id) const
{
    weakref_impl* const refs = mRefs;
    refs->removeStrongRef(id);  //调试使用,可以不理
    const int32_t c = refs->mStrong.fetch_sub(1, std::memory_order_release); //强引用计数减1

    //之前的引用计数只剩下1时,会删除object内存
    if (c == 1) {
        std::atomic_thread_fence(std::memory_order_acquire);
        refs->mBase->onLastStrongRef(id); //RefBase为空方法,可由子类继承实现
        int32_t flags = refs->mFlags.load(std::memory_order_relaxed);
        if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
            delete this;
            // The destructor does not delete refs in this case.
        }
    }

    refs->decWeak(id); //减少弱引用计数
}

对于强指针,主要关注的是对强引用计数mStrong的操作,原理跟Light Pointer。 这里还有有对弱指针进行操作,在下面再对wp进行介绍

弱指针

强指针的使用会带来另一个问题,对象互相引用,比如

代码语言:javascript
复制
class A {
    B *b;
}

class B {
    A *a;
}

如果A 指向了B,B又指向了,则会带来类似死锁的问题。解决的方法就是一个应用采用强指针,另一个采用弱指针,当强指针计数为0时,无论弱指针计数是否为0,都可以delete掉该内存。但这又有一个新问题:使用弱指针的一方访问的对象已经被删除了,这会导致野指针的问题。所以又做了一项规定弱指针必须先升级为强指针才能访问其指向的对象

代码语言:javascript
复制
template <typename T>
class wp
{
public:
    typedef typename RefBase::weakref_type weakref_type;

    inline wp() : m_ptr(nullptr), m_refs(nullptr) { }

    wp(T* other);  // NOLINT(implicit)
    ~wp();

    // Assignment

    wp& operator = (T* other);


    void set_object_and_refs(T* other, weakref_type* refs);

    // promotion to sp
    sp<T> promote() const;   //提升为强指针

private:
    template<typename Y> friend class sp;
    template<typename Y> friend class wp;

    T*              m_ptr;
    weakref_type*   m_refs;
};

弱指针有两个成员指针 m_ptr 指向object,m_refs指向了RefBase中的weakref_type.先看一下其构造函数

代码语言:javascript
复制
template<typename T>
wp<T>::wp(T* other)
    : m_ptr(other)
{
    m_refs = other ? m_refs = other->createWeak(this) : nullptr;
}

RefBase::weakref_type* RefBase::createWeak(const void* id) const
{
    mRefs->incWeak(id);
    return mRefs;
}

template<typename T>
wp<T>::~wp()
{
    if (m_ptr) m_refs->decWeak(this);
}

当一个弱指针被指向某一object时,会调用到createWeak, createWeak会调用incWeak增加弱引用计数,其他的赋值方法都是一样,没必要列出来。析构时会调用decWeak减少弱引用计数

接下来看两个操作弱引用计数的方法

代码语言:javascript
复制
void RefBase::weakref_type::incWeak(const void* id)
{
    weakref_impl* const impl = static_cast<weakref_impl*>(this);
    impl->addWeakRef(id);
    const int32_t c __unused = impl->mWeak.fetch_add(1,
            std::memory_order_relaxed);
    ALOG_ASSERT(c >= 0, "incWeak called on %p after last weak ref", this);
}


void RefBase::weakref_type::decWeak(const void* id)
{
    weakref_impl* const impl = static_cast<weakref_impl*>(this);
    impl->removeWeakRef(id);
    const int32_t c = impl->mWeak.fetch_sub(1, std::memory_order_release);
    LOG_ALWAYS_FATAL_IF(BAD_WEAK(c), "decWeak called on %p too many times",
            this);
    if (c != 1) return;
    atomic_thread_fence(std::memory_order_acquire);

    int32_t flags = impl->mFlags.load(std::memory_order_relaxed);
    if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
        // This is the regular lifetime case. The object is destroyed
        // when the last strong reference goes away. Since weakref_impl
        // outlives the object, it is not destroyed in the dtor, and
        // we'll have to do it here.
        if (impl->mStrong.load(std::memory_order_relaxed)
                == INITIAL_STRONG_VALUE) {
            // Decrementing a weak count to zero when object never had a strong
            // reference.  We assume it acquired a weak reference early, e.g.
            // in the constructor, and will eventually be properly destroyed,
            // usually via incrementing and decrementing the strong count.
            // Thus we no longer do anything here.  We log this case, since it
            // seems to be extremely rare, and should not normally occur. We
            // used to deallocate mBase here, so this may now indicate a leak.
            ALOGW("RefBase: Object at %p lost last weak reference "
                    "before it had a strong reference", impl->mBase);
        } else {
            // ALOGV("Freeing refs %p of old RefBase %p\n", this, impl->mBase);
            delete impl;
        }
    } else {
        // This is the OBJECT_LIFETIME_WEAK case. The last weak-reference
        // is gone, we can destroy the object.
        impl->mBase->onLastWeakRef(id);
        delete impl->mBase;
    }
}

在减弱指针计数后,当c(弱指针计数)不为1,则直接返回. 当c为1,做了一些判断: 首先 对flag做了判断,

代码语言:javascript
复制
//! Flags for extendObjectLifetime()
    enum {
        OBJECT_LIFETIME_STRONG  = 0x0000,
        OBJECT_LIFETIME_WEAK    = 0x0001,
        OBJECT_LIFETIME_MASK    = 0x0001
    };

enum是目标对象的生命周期,每个目标对象可以通过extendObjectLifetime来修改其生命周期(我也不太懂这里)。如果不去修改,默认情况下flag都为OBJECT_LIFETIME_STRONG,故会进到if判断里面去。 对于这个if判断的逻辑,这里我也不是很理解,只能直白地说下字面意思,在增加或减少强指针计数时,会同时整加或减少弱指针计数,而在对弱指针计数的操作则不会同时对强指针计数进行操作,所以 弱指针计数 >= 强指针计数 如果impl->mStrong 为INITIAL_STRONG_VALUE,表示从没被强引用过则不做任何操作,有可能对象之前被弱引用,但是已经被适当地销毁了。所以不用做任何事情。如果impl->mStrong 不为INITIAL_STRONG_VALUE,即弱指针计数和 强指针计数同时为0,这时候删除impl, 即RefBase 的 weakref_impl* const mRefs对象。

关于sp和wp, sp 的incStrong 会同时增加sp和wp计数, wp计数通过incWeak操作 wp 的incWeak 只会增加wp计数 sp 的decStrong 会减小sp计数和wp计数,当sp计数为0时会delete object,object是继承RefBase的对象,wp计数通过decWeak操作 wp 的decWeak只会减小wp计数, 如果wp计数为0,如果object从未被强引用则不做任何操作(Android 10的逻辑,之前好像会delete impl->mBase,即object对象),否则会delete RefBase 的 weakref_impl* const mRefs对象

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 轻量级指针
    • 指针的问题
      • 解决指针问题
        • 智能指针是类
      • 计数问题
      • 强指针
      • 弱指针
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档