27
2017
09

智能指针

智能指针

总体描述

1.简介

1.指针问题的常见来源

  • 指针没有初始化
  • new了对象后没有及时delete
  • 野指针

通过delete释放了对象,但没有将指针置空。

header 1 header 2
row 1 col 1 row 1 col 2
row 2 col 1 row 2 col 2

2.如何设计一个智能指针

考虑的因素:

  1. 初始化;
  2. 实现new和delete的配套;

具体的设计实现:

我们将智能指针称为SmartPointer。

  • SmartPointer是一个类

首先想到的是,SmartPointer要能记录内存对象Object的地址,它的内部应该有一个变量指向Object,所以SmartPointer是一个类。

class SmartPointer{
    private:
        void* m_ptr;//指向Object对象
}
  • SmartPointer是一个模板类

智能指针并不是针对某种特定类型的对象而设计的,因而一定是模板类。

template <typename T> class SmartPointer{
    private:
        T* m_ptr;//指向Object对象
}
  • SmartPointer的构造函数

智能指针需要考虑的一个问题就是初始化,所以智能指针的构造函数应将m_ptr置空。

template <typename T> class SmartPointer{
    inline SmartPointer() : m_ptr(0) {}
    private:
        T *m_ptr;//指向Object
}
  • 引用计数

智能指针还需考虑的一个问题是什么时候释放一个内存对象?可以考虑通过引用计数来统计对象使用的次数,如果对象的引用次数为0了,则可以释放该对象了。引用计数该由谁来管理呢?

如果由智能指针自己来维护引用计数的话,将会出现引用计数不一致的情况,导致致命错误。

如果由引用对象Object自己维护计数器,则可以解决此问题。我们可以定义一个统一的具备计数功能的父类Object。

template <class T> class LightRefBase{
    public:
        inline LightRefBase() : mCount(0) {}
        // 增加引用计数
        inline void incStrong() const {
            android_atomic_inc(&mCount);
        }
        // 减小引用计数
        inline void decStrong() const {
            if(android_atomic_dec(&mCount) == 1){
                delete static_cast<const T*>(this);//删除内存对象
            }
        }
    protected:
        inline ~LightBaseRef() {}
    private:
        muteable volatile int32_t mCount;//引用计数值
}

为此智能指针SmartPointer需要重载“=”运算符,其定义也需要修改:

template <typename T> class SmartPointer{
    inline SmartPointer() : m_ptr(0) {}
    ~SmartPointer();
    SmartPointer& operator = (T* other);//重载运算符

    private:
        T* m_ptr;
}

template <typename T> SmartPointer<T>& SmartPointer<T>::operator = (T *other){
    if(other != null){
        other->incStrong();//主动增加计数值
    }
    if(m_ptr){
        m_ptr->decStrong();// 避免重复赋值的情况
    }
    m_ptr = other;//指向内存对象Object
    return *this;
}

template <typename T> SmartPointer<T>::~SmartPointer(){
    if(m_ptr){
        m_ptr->decStrong();
    }
}

2.强指针sp

sp是StrongPointer的简写,StrongPointer定义在frameworks/native/include/utils/StrongPointer.h

sp类的定义如下:

template <typename T>
class sp
{
public:
inline sp() : m_ptr(0) { }

sp(T* other);
sp(const sp<T>& other);
.......其他构造函数
~sp();// 析构函数


//重载运算符
sp& operator = (T* other);
sp& operator = (const sp<T>& other);
.....
void force_set(T* other);

void clear();
// 重载访问符
inline  T&      operator* () const  { return *m_ptr; }
inline  T*      operator-> () const { return m_ptr;  }
inline  T*      get() const         { return m_ptr; }

private:
template<typename Y> friend class sp;
template<typename Y> friend class wp;
void set_pointer(T* ptr);
T* m_ptr;//指向引用对象
};

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>
void sp<T>::force_set(T* other)
{
    other->forceIncStrong(this);
    m_ptr = other;
}

template<typename T>
void sp<T>::clear()
{
    if (m_ptr) {
        m_ptr->decStrong(this);
        m_ptr = 0;
}
}

template<typename T>
void sp<T>::set_pointer(T* ptr) {
    m_ptr = ptr;
}

LightRefBase类的定义如下:

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);
        }
    }

typedef LightRefBase<T> basetype;

protected:
    inline ~LightRefBase() { }

private:
mutable volatile int32_t mCount;//引用计数值
};

3.弱指针wp

wp是WeakPointer的简写,弱指针的出现是为了解决循环引用的问题。

双方规定:

当强引用计数为0时,不论弱引用是否为0都可以delete自己。为了避免野指针的出现,还有一个规定:弱指针必须先升级为强指针,才能访问它所指向的目标对象。

弱指针的主要使命就是解决循环引用的问题。

与sp相比,wp在定义上有如下重要的区别:

  • 除了指向目标对象的m_ptr外,wp另外有一个m_refs指针,类型为weakref_type;
  • 没有重载->,*等运算符。
  • 有一个promote方法来将wp提升为sp
  • 目标对象的父类不是LightRefBase,而是RefBase。

弱指针与强指针一个很大的区别是,弱指针不能直接操作它所引用的对象,因为它所引用的对象可能是不受弱引用计数控制的,即它所引用的对象可能是一个无效的对象。因此,如果需要操作一个弱指针所引用的对象,那么需要将这个弱指针升级promote为强指针,如果升级成功,则说明该弱指针所引用的对象还没有被销毁,可以正常使用。

wp类的定义如下:

template <typename T>
class wp
{
public:
    typedef typename RefBase::weakref_type weakref_type;
        inline wp() : m_ptr(nullptr) { }

    wp(T* other);//构造函数

    ~wp();//析构函数

    wp& operator = (T* other);// 运算符重载

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

    sp<T> promote() const;// 升级为强指针

    void clear();

    inline  weakref_type* get_refs() const { return m_refs; }

    inline  T* unsafe_get() const { return m_ptr; }

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)
{
    if (other) m_refs = other->createWeak(this);
}

在sp的构造函数中,调用的incStrong方法增加引用计数,而wp没有直接增加目标对象的引用计数值,而是调用了createWeak方法。该方法定义在RefBase类中。

RefBase类的定义如下:

class RefBase
{
    public:
        void            incStrong(const void* id) const;// 增加强引用计数
        void            decStrong(const void* id) const;// 减小强引用计数
// 嵌套内部类,wp中用到的就是这个类
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);
};

weakref_type*   createWeak(const void* id) const;// 生成一个weakref_type类型

weakref_type*   getWeakRefs() const;

typedef RefBase basetype;

protected:
                        RefBase();// 构造函数
virtual                 ~RefBase();// 析构函数

// 以下参数用于修改Object的生命周期
enum {
    OBJECT_LIFETIME_STRONG  = 0x0000,
    OBJECT_LIFETIME_WEAK    = 0x0001,
    OBJECT_LIFETIME_MASK    = 0x0001
};

void  extendObjectLifetime(int32_t mode);


enum {
    FIRST_INC_STRONG = 0x0001
};

virtual void            onFirstRef();
virtual void            onLastStrongRef(const void* id);
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);
    weakref_impl* const mRefs;
};

RefBase嵌套了一个重要的类weakref_type,也就是wp中的m_refs所属的类型。在RefBase中还有一个mRefs的成员变量,类型为weakref_type_impl,该类型是weakref_type的实现类。

RefBase类与LightRefBase类不一样,它不是直接使用一个整数来维护对象的引用计数的,而是使用一个weakref_impl对象,即成员变量mRefs来描述对象的引用计数。

weakef_impl类同时为对象提供了强引用计数和弱引用计数,weakref_impl的类型如下:

#define INITIAL_STRONG_VALUE (1<<28)
class RefBase::weakref_impl : public RefBase::weakref_type
{
public:
volatile int32_t    mStrong;//强引用计数
volatile int32_t    mWeak;//弱引用计数
RefBase* const      mBase;//指向所引用的对象的地址
volatile int32_t    mFlags;//描述对象的生命周期控制方式
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 addWeakRef(const void* /*id*/) { }
void removeWeakRef(const void* /*id*/) { }
}

wp类与RefBase、weakref_type以及weakref_type_impl之间的关系如下:

智能指针

weakref_impl类继承了weakref_type类。weakref_type类定义在RefBase类的内部,它提供了成员函数incWeak、decWeak、attemptIncStrong和attemptIncWeak来维护对象的强引用计数和弱引用计数。weakref_type类只定义了引用计数维护接口,具体的实现是由weakref_impl类提供的。

weakref_impl类有两个成员变量mStrong和mWeak,分别用来描述对象的强引用计数和弱引用计数。weakref_impl类的成员变量mBase指向了它所引用的对象的地址,而成员变量mFlags是一个标志值,用来描述对象的生命周期控制方式。其中mFlags的取值范围为以下几种:

  • OBJECT_LIFETIME_STRONG,表示对象的生命周期只受强引用计数影响;
  • OBJECT_LIFETIME_WEAK,表示对象的生命周期同时受强引用计数和弱引用计数影响;
  • OBJECT_LIFETIME_FOREVER,表示对象的生命周期完全不受强引用计数或者弱引用计数影响;

4.重要函数实现

接下来看几个比较重要方法的实现:

1.RefBase:createWeak()

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

该函数首先增加了mRefs中的弱引用计数值,然后返回这个mRefs。mRefs是一个weakref_type类型,具体的incWeak方法实现如下:

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 = android_atomic_inc(&impl->mWeak);
ALOG_ASSERT(c >= 0, "incWeak called on %p after last weak ref", this);
}

weakref_impl中的mWeak是一个整型变量,记录的是弱引用计数。

可以看到,RefBase和LightRefBase不同,LightRefBase是直接采用int变量来保存引用计数值,而RefBase是采用了weakref_type类型的计数器。这是因为RefBase需要处理多种计数类型。wp和RefBase都指向了weakref_type类型的计数器。

wp与sp相比,只是采用了一种新的计数器weakref_impl而已,其他的工作都是围绕如何操作这个计数器而展开的。

2.RefBase::incStrong()

当wp构造完成后,RefBase所持有的weakref_type计数器的mWeak值就为1,后面如果有新的wp指向这个对象,mWeak还会持续增加。如果是sp呢?

sp会调用目标对象的incStrong方法来增加强引用计数值,当目标对象继承自RefBase时,这个函数的实现就是:

void RefBase::incStrong(const void* id) const
{
weakref_impl* const refs = mRefs;
refs->incWeak(id);// 1.增加弱引用计数

refs->addStrongRef(id);
const int32_t c = android_atomic_inc(&refs->mStrong);//2.增加强引用计数
ALOG_ASSERT(c > 0, "incStrong() called on %p after last strong ref", refs);
// 判断是不是第一次
if (c != INITIAL_STRONG_VALUE)  {
    return;// 不是第一次,则直接返回
}
// 给mStrong赋初始值为1 
android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong);
// 3.回调onFirstRef通知对象自己被引用了
refs->mBase->onFirstRef();
}

该方法的主要功能:

  • 增加强引用计数值;
  • 增加弱引用计数值;
  • 分别增加weakref_impl的强弱引用计数(mStrong/mWeak),进行加1操作;

android_atomic_inc函数是返回值是对象原来的强引用计数值,即加1之前的值。在weakref_impl类的构造函数中,成员变量mStrong的值被初始化为INITIAL_STRONG_VALUE,该值定义如下:

#define INITIAL_STRONG_VALUE (1<<28)

从理论上说,当对象第一次被强指针引用时,它的强引用计数值应该等于1,但对象的初始值为INITIAL_STRONG_VALUE,所以需要调用android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong)方法进行调整。

从RefBase的incStrong()方法可以知道,增加对象的强引用计数的同时,也会增加对象的弱引用计数,所以对象的弱引用计数值一定大于强引用计数值。

3.RefBase::weakref_type::incWeak()

void RefBase::weakref_type::incWeak(const void* id)
{
    weakref_impl* const impl = static_cast<weakref_impl*>(this);
    impl->addWeakRef(id);// 调试使用
    const int32_t c = android_atomic_inc(&impl->mWeak);//增加弱引用计数值
    LOG_ASSERT(c >= 0, "incWeak called on %p after last weak ref", this);
}

incWeak()方法主要是增加mRefs变量中的mWeak成员值,即增加弱引用计数值;

4.RefBase::decStrong()

void RefBase::decStrong(const void* id) const
{
weakref_impl* const refs = mRefs;
refs->removeStrongRef(id);// 调试目的
const int32_t c = android_atomic_dec(&refs->mStrong);// 1.减少强引用计数
ALOG_ASSERT(c >= 1, "decStrong() called on %p too many times", refs);
// 减小后强引用计数值已经为0了
if (c == 1) {
    refs->mBase->onLastStrongRef(id);//通知事件
    if ((refs->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {//对象的生命周期是否受强引用计数控制
        delete this;// 删除对象
    }
}
refs->decWeak(id); // 2.减少弱引用计数
}

在该方法中,首先减少强引用计数,如果发现已经减到0(即c==1),就需要回调onLastStrongRef方法通知该事件。若对象是受强引用计数控制,则执行删除操作(如果标志位是OBJECT_LIFETIME_STRONG),导致调用RefBase的析构函数。

RefBase::~RefBase()
{
    ......
    if (mRefs->mWeak == 0) {
        delete mRefs;
    }
    .......
}

在RefBase类的析构函数中,如果发现对象的弱引用计数值为0,那么就会把引用计数对象mRefs也一起释放了。RefBase类的成员变量mRefs指向的是一个weakref_impl对象,它是在RefBase类的构造函数中创建的。现在既然它所属的RefBase对象已经不存在了,并且它所引用的对象的弱引用计数值也等于0,它也就不需要存在了。一个对象的弱引用计数一定是大于等于它的强引用计数的,因此,当一个对象的强引用计数值为0时,它的弱引用计数值可能大于0。在对象的弱引用计数值大于0的情况下,我们只能将对应的RefBase对象释放掉,而不能将该RefBase对象内部的weakref_impl对象也释放掉,因为还有其他的弱指针通过该weakref_impl对象来引用实际的对象。只有当对象的弱引用计数值也为0 时,才可以将该weakref_impl对象也一起释放掉。

最后减少弱引用计数值。

5.RefBase::weakref_type::decWeak()

void RefBase::weakref_type::decWeak(const void* id)
{
weakref_impl* const impl = static_cast<weakref_impl*>(this);
impl->removeWeakRef(id);// 调试目的
const int32_t c = android_atomic_dec(&impl->mWeak);// 减小弱引用计数值
ALOG_ASSERT(c >= 1, "decWeak called on %p too many times", this);
如果发现还不为0,即c != 1,就直接返回;否则弱引用的计数值也为0,此时需要根据LIFETIME标志分别处理。
if (c != 1) return;

// 1.当标志为OBJECT_LIFETIME_STRONG时,释放规则受强引用控制
if ((impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_STRONG) {
    // 强引用值为INITIAL_STRONG_VALUE,说明该目标对象没有被强引用过
    if (impl->mStrong == INITIAL_STRONG_VALUE) {
        delete impl->mBase;//释放目标对象
    } else {
        // 在有强引用的情况下,此时要释放weakref_impl对象,而目标对象会由强引用的decStrong来释放
        delete impl;//释放引用计数对象
    }
} else {
    impl->mBase->onLastWeakRef(id);// 通知最后一个弱引用事件
    if ((impl->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_WEAK) {
        delete impl->mBase;
    }
}
}

如果弱引用计数值为0,则它的强引用计数值也一定为0。在对象的弱引用计数值等于0时,需要考虑释放需要将对象释放掉。这取决于对象的生命周期控制方式,以及该对象是否被强指针引用过。

第一种情况是对象的生命周期只受强引用计数控制。如果对象从来没有被强指针引用过,那么在对象的弱引用计数值等于0时,将该对象释放掉。如果对象被强指针引用过,则释放该对象内部的引用计数器对象weakref_impl,因为该引用对象在RefBase类的成员函数decStrong方法中已经被释放掉了。

第二种情况是对象的生命周期受弱引用计数控制。则首先调用对象的成员函数onLastWeakRef来使得它可以执行一些业务相关的逻辑,同时对于不受强引用计数和弱引用计数的对象,则需要释放该对象。

当对象的生命周期控制标志值设置为OBJECT_LIFETIME_FOREVER时,即对象的生命周期完全不受强引用计数和弱引用计数控制时,Android系统所提供的智能指针就退化成一个普通的C++指针了,这时候开发人员就需要手动地释放那些不再使用了的对象所占用的内存。

所引用主要是基于以下标志来处理的:

enum {
    OBJECT_LIFETIME_STRONG  = 0x0000,// 强引用生命周期
    OBJECT_LIFETIME_WEAK    = 0x0001,// 弱引用生命周期
    OBJECT_LIFETIME_MASK    = 0x0001
};

每个目标对象都可以通过以下方法来更改它的引用规则:

void RefBase::extendObjectLifetime(int32_t mode)
{
    android_atomic_or(mode, &mRefs->mFlags);
}

生命周期

  • flags为OBJECT_LIFETIME_STRONG时,强引用计数控制实际对象的生命周期,弱引用计数控制weakref_impl对象的生命周期。

    • 强引用计数为0时,实际对象被delete。对于这种情况,应使用wp时由弱生强promote()。
  • flags为OBJECT_LIFETIME_WEAK时,强引用计数为0,弱引用计数不为0时,实际对象不会被delete。

    • 当弱引用计数减为0时,实际对象和weakref_impl对象会同时被delete。
  • flags为LIFETIME_FOREVER,对象不受强弱引用计数的控制,永不会被回收。

小结:

  • 如果一个对象的生命周期控制标志值为OBJECT_LIFETIME_STRONG,则只要它的强引用计数值为0,系统才会自动释放该对象。
  • 如果一个对象的生命周期控制标志值为OBJECT_LIFETIME_WEAK,则只有当它的强引用计数和弱引用计数值都为0时,系统才会自动释放该对象。
  • 如果一个对象的生命周期控制标志为OBJECT_LIFETIME_FOREVER,那么系统就 永远不会自动释放这个对象,它需要由开发人员来手动地释放。

6.RefBase::RefBase和RefBase::~RefBase()

最后来看下RefBase的构造函数和析构函数:

构造函数

RefBase::RefBase()
: mRefs(new weakref_impl(this))
{
}

析构函数

RefBase::~RefBase()
{

if (mRefs->mStrong == INITIAL_STRONG_VALUE) {//对象未被强指针引用过
    delete mRefs;//删除引用计数器对象
} else {
    if ((mRefs->mFlags & OBJECT_LIFETIME_MASK) != OBJECT_LIFETIME_STRONG) {
        // 弱引用计数为0
        if (mRefs->mWeak == 0) {
            delete mRefs;// 释放weakref_impl对象
        }
    }
}
const_cast<weakref_impl*&>(mRefs) = NULL;
}

7.wp::promote()

template<typename T>
sp<T> wp<T>::promote() const
{
    sp<T> result;
    // m_ptr不为空,且增加强引用计数成功
    if (m_ptr && m_refs->attemptIncStrong(&result)) {
        result.set_pointer(m_ptr);
    }
    return result;
}

// 设置强引用指针
template<typename T>
void sp<T>::set_pointer(T* ptr) {
    m_ptr = ptr;
}

该方法是将弱指针转换为强指针。如果能成功增加对象的强引用计数值,那么就可以将一个弱指针升级为一个强指针。

5.总结

  1. 智能指针分为强指针sp和弱指针wp两种;
  2. 通常情况下,目标对象的父类都是RefBase——这个基类提供一个weakref_impl类型的引用计数器,可以同时进行强、弱引用的控制(内部由mStrong和mWeak提供计数);
  3. 当incStrong增加强引用计数时,也会增加弱引用计数;
  4. 当incWeak时只增加弱引用计数;
  5. 使用者也可以通过extendObjectLifetime设置引用计数器的规则,不同规则下对删除目标对象的时机判断也是不一样的。
  6. 使用者可以根据程序需求来选择合适的智能指针类型和计数器规则。
  7. 如果引用对象的生命周期受强指针控制,则只要对象的强引用计数值为0,系统就会自动释放该对象;如果引用对象的生命周期受弱指针控制,则只有对象的强引用计数和弱引用计数都为0时,系统才会自动释放该对象。如果引用对象的生命周期不受强指针和弱指针控制,则需要手动释放该引用对象。

参考链接:理解Refbase强弱引用
强指针的实现原理分析
弱指针的实现原理分析

上一篇:第四章 ReactNative与原生之间的接口交互 下一篇:将Android.mk转换成Cmake使用