C++ RAII对象
RAII的全称是Resource Acquisition Is Initialization,即“资源获取就是初始化”。RAII的做法是使用一个对象,在其构造时获取对应的资源,在对象生命期内控制对资源的访问,使之始终保持有效,最后在对象析构的时候,释放构造时获取的资源。把资源放到对象里面,便可依赖C++的构造函数和析构函数机制,确保对资源的持有和释放。
智能指针便是属于RAII的一种,智能指针是一个用于管理资源的对象,其使用引用计数技术。在智能指针对象构造时,增加它所引用对象的引用计数,在其析构时,减少它所持有对象的引用计数。当引用对象的引用计数为0时,便释放引用对象。
引用计数存放在引用对象中,在智能指针对象构造时,需要将待引用对象传入到智能指针对象的构造函数中,在构造函数会对引用对象的引用计数加1,当智能指针对象析构时,就将其所引用对象的引用计数减1。
Android内置的智能指针包含三种:轻量级指针、强指针、弱指针。
轻量级指针
轻量级指针通过简单的引用计数技术来维护对象的生命周期,下文从代码角度分析下轻量级指针的实现原理。
LightRefBase 类
// code path: /system/core/include/utils/lightRefBase.h
template <class T>
class LightRefBase
{
public:
inline LightRefBase() : mCount(0) { }
inline void incStrong(__attribute__((unused)) const void* id) const {
mCount.fetch_add(1, std::memory_order_relaxed);
}
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);
delete static_cast<const T*>(this);
}
}
//! DEBUGGING ONLY: Get current strong ref count.
inline int32_t getStrongCount() const {
return mCount.load(std::memory_order_relaxed);
}
typedef LightRefBase<T> basetype;
protected:
inline ~LightRefBase() { }
private:
friend class ReferenceMover;
inline static void renameRefs(size_t /*n*/, const ReferenceRenamer& /*renamer*/) { }
inline static void renameRefId(T* /*ref*/, const void* /*old_id*/ , const void* /*new_id*/) { }
private:
mutable std::atomic<int32_t> mCount; // 对象的引用计数
};
任何需要使用轻量级指针的类对象都必须要继承 LightRefBase 类。LightRefBase是一个模板类,其中模板参数T表示对象的实际类型,它必须是继承了 LightRefBase 类的。其成员变量mCount用来描述对象的的引用计数值,提供了两个public接口 incStrong 和 decStrong 来增加和减少对象的引用计数值。在成员函数decStrong中,当对象的引用计数值为1时,其减少之后就会变成0,就表示需要需要释放这个对象所占用的内存了。
轻量级指针的实现类 sp
我们有了轻量级指针所指向的对象,那么这个对象由谁来指向?即谁来调用对象的 incStrong 和 decStrong 函数?接下来需要真正的轻量级指针的实现类,Android提供的轻量级指针的实现类是 sp ,它也是强指针的实现类,这里仅关注它与轻量级指针相关的实现。
template<typename T>
class sp {
public:
inline sp() : m_ptr(nullptr) { }
sp(T* other); // NOLINT(implicit)
sp(const sp<T>& other);
sp(sp<T>&& other);
template<typename U> sp(U* other); // NOLINT(implicit)
template<typename U> sp(const sp<U>& other); // NOLINT(implicit)
template<typename U> sp(sp<U>&& other); // NOLINT(implicit)
~sp();
// Assignment
sp& operator = (T* other);
sp& operator = (const sp<T>& other);
sp& operator = (sp<T>&& other);
template<typename U> sp& operator = (const sp<U>& other);
template<typename U> sp& operator = (sp<U>&& other);
template<typename U> sp& operator = (U* other);
//! Special optimization for use by ProcessState (and nobody else).
void force_set(T* other);
// Reset
void clear();
// Accessors
inline T& operator* () const { return *m_ptr; }
inline T* operator-> () const { return m_ptr; }
inline T* get() const { return m_ptr; }
inline explicit operator bool () const { return m_ptr != nullptr; }
// Operators
COMPARE(==)
COMPARE(!=)
COMPARE(>)
COMPARE(<)
COMPARE(<=)
COMPARE(>=)
private:
template<typename Y> friend class sp;
template<typename Y> friend class wp;
void set_pointer(T* ptr);
T* m_ptr;
};
sp 类也是一个模板类,模板参数T表示所引用对象的实际类型,该类型必须继承自 LightRefBase 类。sp 也是强指针的实现类,这里仅关注其对于轻量级指针的实现:
T* m_prt; //该成员变量用于指向所引用的对象,该对象必须继承自 LightRefBase
template<typename T>
sp<T>::sp(T* other)
: m_ptr(other) {
if (other)
other->incStrong(this);
}
template<typename T>
sp<T>::sp(const sp<T>& other)
: m_ptr(other.m_ptr) {
if (m_ptr)
m_ptr->incStrong(this);
}
template<typename T>
sp<T>& sp<T>::operator =(T* other) {
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
if (other) other->incStrong(this);
if (oldPtr) oldPtr->decStrong(this);
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
m_ptr = other;
return *this;
}
template<typename T>
sp<T>& sp<T>::operator =(const sp<T>& other) {
// Force m_ptr to be read twice, to heuristically check for data races.
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
T* otherPtr(other.m_ptr);
if (otherPtr) otherPtr->incStrong(this);
if (oldPtr) oldPtr->decStrong(this);
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
m_ptr = otherPtr;
return *this;
}
template<typename T>
sp<T>::~sp() {
if (m_ptr)
m_ptr->decStrong(this);
}
sp 类和轻量级相关的就是它的构造函数、赋值操作符以及、析构函数。构造函数和析构函数的逻辑非常简单,构造时将所引用对象的引用计数加1,析构时将所引用对象的引用计数减1,都是调用 LightRefBase 的接口。
它的赋值操作符的逻辑就会稍有点复杂,再次先回忆下智能指针的原理:当引用对象有智能指针指向它时,就会给它的引用计数加1;当指向了所引用对象的智能指针不在指向改对象时,它的引用计数就会减1。赋值函数的逻辑同理,由于智能指针指向的新的对象,就会增加新对象的引用计数,而减少旧对象的引用计数。
UML类图
强指针和弱指针
轻量级指针属于简单引用计数技术,它存在这样一个缺陷:两个对象互相通过智能指针引用时,最终都无法通过智能指针析构。 如下:
class A: public LightRefBase<A> {
sp<B> pB;
};
class B: public LightRefBase<B> {
sp<A> pA:
}
sp<A> p_a = new A();
sp<B> p_b = new B();
p_a->pB = p_b;
p_b->pA = p_a;
p_a = NULL;
p_b = NULL;
因此,需要一种更为复杂的引用计数技术来维护对象的声明周期,即强引用计数和弱引用计数。这种新的引用计数方式,有种用法可以规定对象的生命周期只受强引用计数控制。例:上述对象A强引用B,对象B弱引用A,当p_a和p_b置为NULL后,对象A就可以析构,然后对象B也会被析构。
引用计数类——RefBase
强弱引用计数类的实现实现如下(删除了相关的bug代码,仅保留精华实现代码):
// path: system/core/libutils/include/utils/RefBase.h
class RefBase
{
public:
void incStrong(const void* id) const; // 增加强引用计数和弱引用计数
void decStrong(const void* id) const; // 减少强应用计数和弱引用计数
class weakref_type
{
public:
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; // 返回weakref_type*
protected:
RefBase();
virtual ~RefBase();
enum { //! Flags for extendObjectLifetime()
OBJECT_LIFETIME_STRONG = 0x0000, // 由强引用计数维护对象生命周期(默认值)
OBJECT_LIFETIME_WEAK = 0x0001, // 由弱引用计数维护对象生命周期
OBJECT_LIFETIME_MASK = 0x0001
};
void extendObjectLifetime(int32_t mode);
// 当对象的生命周期受弱引用计数控制时,该函数判断是否允许将其升级到强指针
enum { //! Flags for onIncStrongAttempted()
FIRST_INC_STRONG = 0x0001
};
virtual bool onIncStrongAttempted(uint32_t flags, const void* id); // 对象的生命周期
virtual void onFirstRef(); // 对象第一次被强指针引用
virtual void onLastStrongRef(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; // 描述引用计数的对象
};
// path: system/core/libutils/RefBase.cpp
#define INITIAL_STRONG_VALUE (1<<28)
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; // 对象生命周期控制的flag:强引用计数控制/弱引用计数控制
// 构造函数给mStrong的初始值不是0,这里很有意思。应该是用于区分对象是否是第一次被sp引用。
explicit weakref_impl(RefBase* base): mStrong(INITIAL_STRONG_VALUE), mWeak(0), mBase(base), mFlags(0) {}
};
同LightRefBase类,RefBase也提供了相关成员函数incStrong()和decStrong来增加、减少它所维护对象的引用计数。比较不同的一点是RefBase不是直接使用一个整数来描述引用计数,而是新增了一个 weakref_impl 对象来描述强引用计数和弱引用计数。
提供的用于维护引用计数的主要接口:
- incWeak
- decWeak
- attemptIncStrong
- attemptIncWeak
提供的用于维护引用计数的主要成员:
- mStrong —— 描述强引用计数
- mWeak —— 描述弱引用计数
- mBase —— 指向引用对象的指针
- mFlags —— 对象生命周期的flag
这里的类组织可能有点迷惑:
- RefBase类提供了直接用于操作强引用计数的接口:incStrong和decStrong。
- 具体弱引用计数的操作则完全封装到了它的内部类:weakref_type中。
强指针实现类——sp
如前文所LightRefBase的智能指针实现类sp,该类也是RefBase的强指针实现类,它的构造函数和析构函数调用调用相应的incStrong()和decStrong()。对应到RefBase的流程如下:
void RefBase::incStrong(const void* id) const
{
weakref_impl* const refs = mRefs;
refs->incWeak(id);
const int32_t c = refs->mStrong.fetch_add(1, std::memory_order_relaxed);
if (c != INITIAL_STRONG_VALUE) {
return;
}
int32_t old __unused = refs->mStrong.fetch_sub(INITIAL_STRONG_VALUE, std::memory_order_relaxed);
refs->mBase->onFirstRef();
}
void RefBase::weakref_type::incWeak(const void* id)
{
weakref_impl* const impl = static_cast<weakref_impl*>(this);
const int32_t c __unused = impl->mWeak.fetch_add(1, std::memory_order_relaxed);
}
这段代码做了三件事:
• 增加弱引用计数
• 增加强引用计数
• 该对象若是第一次被强指针引用,就调用它的onFirstRef函数
注意:由于强引用计数的初始值是INITIAL_STRONG_VALUE,所以如果是第一次被引用,需要减掉这个值。这个值的意义就是用于判断是否第一次被引用,不能是用0来判断是否第一次被引用。
void RefBase::decStrong(const void* id) const
{
weakref_impl* const refs = mRefs;
const int32_t c = refs->mStrong.fetch_sub(1, std::memory_order_release);
if (c == 1) {
std::atomic_thread_fence(std::memory_order_acquire);
refs->mBase->onLastStrongRef(id);
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);
}
RefBase::~RefBase()
{
int32_t flags = mRefs->mFlags.load(std::memory_order_relaxed);
if ((flags & OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_WEAK) {
if (mRefs->mWeak.load(std::memory_order_relaxed) == 0) {
delete mRefs;
}
}
const_cast<weakref_impl*&>(mRefs) = nullptr;
}
void RefBase::weakref_type::decWeak(const void* id)
{
weakref_impl* const impl = static_cast<weakref_impl*>(this);
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);
if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
if (impl->mStrong.load(std::memory_order_relaxed)
== INITIAL_STRONG_VALUE) {
ALOGW("RefBase: Object at %p lost last weak reference "
"before it had a strong reference", impl->mBase);
} else {
delete impl;
}
} else {
impl->mBase->onLastWeakRef(id);
delete impl->mBase;
}
}
decStrong主要做四件事:
- 减少强引用计数
- 当减少之后的强引用计数为0时,调用onLastStrongRef函数
- 当减少之后的强引用计数为0并且引用对象的生命周期控制由强引用计数决定,就delete改引用对象
- 减少弱引用计数
RefBase的析构函数实现也很简单:
- 判断引用对象的生命周期是否有弱引用计数决定,如果是并且当前弱引用计数已经为0,就delete内部的mRefs对象。(这样做的原因是因为弱引用计数不为0,有可能有其它的wp在拉着这个对象,所以不能delete mRefs,否则会导致wp出现野指针)
从上文的分析可以看到,incStrong会增加对象的强引用计数和弱引用计数。相应的,decStrong也会减少对象的强引用计数和弱引用计数。
decWeak的主要实现:
- 减少对象的弱引用计数
- 如果减少之后的弱引用计数不为0,就直接return
- 当减少之后的弱引用计数不为0,并且对象的生命周期由强引用计数决定,如果此刻强引用计数是初始值,说明第一次new 引用对象直接是给wp的,此刻什么也没做(PS:这里很容易出现mem leak,所以给了一个warning log);若此刻强引用计数不是初始值,就直接delete weakref_impl对象,因为此时强弱引用计数都为0,已经不需要它了
- 当减少之后的弱引用高技术不为0,并且对象的生命周期由弱引用计数决定,调用onLastWeakRefs函数,然后delete引用对象(PS:在delete引用对象里面会delete weakref_impl对象)
总结:
- 当mFlags为OBJECT_LIFETIME_STRONG时,强引用计数为0,就释放对象。
- 当mFlags为OBJECT_LIFETIME_WEAK 时,弱引用计数为0,就释放对象。
- RefBase对象的弱引用计数总是大于等于强引用计数。
弱指针实现类——wp
template <typename T>
class wp
{
public:
typedef typename RefBase::weakref_type weakref_type;
// 构造和析构函数
inline wp() : m_ptr(nullptr), m_refs(nullptr) { }
wp(T* other);
wp(const wp<T>& other);
explicit wp(const sp<T>& other);
template<typename U> wp(U* other);
template<typename U> wp(const sp<U>& other);
template<typename U> wp(const wp<U>& other);
~wp();
// 赋值运算符
wp& operator = (T* other);
wp& operator = (const wp<T>& other);
wp& operator = (const sp<T>& other);
template<typename U> wp& operator = (U* other);
template<typename U> wp& operator = (const wp<U>& other);
template<typename U> wp& operator = (const sp<U>& other);
sp<T> promote() const; // promotion to sp
void clear();
inline weakref_type* get_refs() const { return m_refs; }
inline T* unsafe_get() const { return m_ptr; }
private:
T* m_ptr;
weakref_type* m_refs;
};
template<typename T>
wp<T>::wp(T* other)
: m_ptr(other)
{
m_refs = other ? m_refs = other->createWeak(this) : nullptr;
}
template<typename T>
wp<T>::~wp()
{
if (m_ptr) m_refs->decWeak(this);
}
相比较于强指针,弱指针的不同点:
- 构造和析构函数只对弱引用计数进行操作
- 新增了promote()函数,如果需要通过wp访问引用对象,必须调用promote(),返回正确的sp之后才能访问引用对象
- 成员变量除了m_ptr指向引用对象之外,新增了m_refs对象。(因为只有该对象提供单独弱引用计数的操作
createWeak()就是增加弱引用计数,decWeak上文已经分析过了,就是减少弱引用计数。所以关于wp的分析主要是对它的promote()函数进行分析,即wp是如何转成sp的?
template<typename T>
sp<T> wp<T>::promote() const
{
sp<T> result;
if (m_ptr && m_refs->attemptIncStrong(&result)) {
result.set_pointer(m_ptr);
}
return result;
}
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);
// case1
while (curCount > 0 && curCount != INITIAL_STRONG_VALUE) { // Q1
if (impl->mStrong.compare_exchange_weak(curCount, curCount+1,
std::memory_order_relaxed)) {
break;
}
}
// case2
if (curCount <= 0 || curCount == INITIAL_STRONG_VALUE) {
// case2.1
int32_t flags = impl->mFlags.load(std::memory_order_relaxed);
if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
// case2.1.1
if (curCount <= 0) {
decWeak(id);
return false;
}
// case2.1.2
while (curCount > 0) {
if (impl->mStrong.compare_exchange_weak(curCount, curCount+1,
std::memory_order_relaxed)) {
break;
}
}
} else {
// case2.2
if (!impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id)) { // Q2
// it didn't so give-up.
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); // Q3
}
}
}
if (curCount == INITIAL_STRONG_VALUE) { // Q4
impl->mStrong.fetch_sub(INITIAL_STRONG_VALUE,
std::memory_order_relaxed);
}
return true;
}
bool RefBase::onIncStrongAttempted(uint32_t flags, const void* /*id*/)
{
return (flags&FIRST_INC_STRONG) ? true : false;
}
promote函数的实现很简单:若m_ptr指向的对象不为null,调用attemptIncStrong()判断是否可以升级成sp。
attemptIncStrong的主要逻辑(参考左边的注释):
-
Case1:当前对象正在被其它sp引用,此时可以转成sp。
-
Case2:当前对象没有被其它sp引用:
- Case2.1:当前对象的生命周期受强引用计数控制
- Case2.1.1:若此刻强引用计数<= 0,说明对象肯定被delete了,此时不能转换。
- Case2.1.2:若此刻引用计数为INITIAL_STRONG_VALUE,说明对象目前位置还没有被sp引用过,new出来之后就被wp拉着了,此时对象肯定是存在的,所以可以转成sp。
- Case2.2:当前对象的生命周期受弱引用计数控制,这说明此时对象肯定是存在的,因为当前至少有一个wp在引用它,所以此时可以转成sp。
- Case2.1:当前对象的生命周期受强引用计数控制
对于attempIncStrong函数的实现,有几个困惑的地方(如左边注释):
- Q1:为什么需要一个while循环,再加一个if判断来代码给mCount+1的目的?
- Ans:可能存在其它线程已经修改了mStrong,所以需要先判断当前mStrong的值和期望值mCount是否相等,如果不相当,将期望值mCount改成mStrong的实际值,并在if中返回false。
- Q2:onIncStrongAttempted的if判断意义何在?
- Ans:
- Q3:为什么需要调用onLastStrongRef,该函数应该是在最后一个sp去掉的时候调用的?
- Ans:
- Q4:第一次被强指针引用,为什么不调用onFirstRef?
- Ans:猜测对于弱引用计数控制的对象,不需要这个概念。
总结
Android智能指针的原理基本上已经分析完了,对于智能指针的使用有几点感想:
- 当出现bug是由于智能指针拉着资源无法释放导致的问题时,非常不好查,从log中很难找到是哪个指针在拉着对象。
- 模块之间的接口设计,很可能是不允许是使用智能指针的,只能是标准的指针,这个时候从智能指针获取标准指针传给对应模块,如何保证标准指针指向的内存不被释放?
上文的分析是把debug的代码都去掉后进行分析的,后续如果有时间可以进一步分析debug的模块。