簡介
我們之前對C++標準庫的智能指針有了一定了解,今天我們來聊一聊Chromium中base的智能指針-scoped_refptr,該智能指針同樣是採用引用計數的方式來控制指針的創建和析構。接下來我們看下源碼來解讀下,源碼在base下的memory/ref_counted.h下
base類
namespace subtle {
class BASE_EXPORT RefCountedBase {
public:
bool HasOneRef() const { return ref_count_ == 1; }
protected:
RefCountedBase();
~RefCountedBase();
void AddRef() const;
// Returns true if the object should self-delete.
bool Release() const;
private:
mutable int ref_count_;
#ifndef NDEBUG
mutable bool in_dtor_;
#endif
DFAKE_MUTEX(add_release_);
DISALLOW_COPY_AND_ASSIGN(RefCountedBase);
};
class BASE_EXPORT RefCountedThreadSafeBase {
public:
bool HasOneRef() const;
protected:
RefCountedThreadSafeBase();
~RefCountedThreadSafeBase();
void AddRef() const;
// Returns true if the object should self-delete.
bool Release() const;
private:
mutable AtomicRefCount ref_count_;
#ifndef NDEBUG
mutable bool in_dtor_;
#endif
DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafeBase);
};
} // namespace subtle
看代碼中有兩個base類(RefCountedBase,RefCountedThreadSafeBase)看名字其實可以知道一個是線程安全的,另一個就是普通的。
RefCountedBase
兩個類長得很像,我們先來看這個普通的base類(RefCountedBase),它的主要成員就是一個ref_count_,這個就是整個智能指針實現的關鍵,引用計數的值,在構造函數裏初始化爲0,在AddRef自增,Release裏自減,如下代碼:
void RefCountedBase::AddRef() const {
// TODO(maruel): Add back once it doesn't assert 500 times/sec.
// Current thread books the critical section "AddRelease" without release it.
// DFAKE_SCOPED_LOCK_THREAD_LOCKED(add_release_);
#ifndef NDEBUG
DCHECK(!in_dtor_);
#endif
++ref_count_;
}
bool RefCountedBase::Release() const {
// TODO(maruel): Add back once it doesn't assert 500 times/sec.
// Current thread books the critical section "AddRelease" without release it.
// DFAKE_SCOPED_LOCK_THREAD_LOCKED(add_release_);
#ifndef NDEBUG
DCHECK(!in_dtor_);
#endif
if (--ref_count_ == 0) {
#ifndef NDEBUG
in_dtor_ = true;
#endif
return true;
}
return false;
}
其中Release在引用計數變爲0時返回true,這個我們後邊講。
RefCountedThreadSafeBase
我們再來看這個線程安全的base類和上邊那個有什麼不同之處,首先既然是線程安全的類,那麼他肯定可以保證他引用計數的線程安全。首先聲明時使用:
AtomicRefCount ref_count_
我們看下AtomicRefCount是個啥,轉到聲明處:
typedef subtle::Atomic32 AtomicRefCount;
typedef int32 Atomic32;
最後發現也只是int32,這是在32位編譯器上,如果編譯64位那就是int64_t了,所以只是平臺相關的整型。那是怎麼來保證線程安全的呢,我們繼續往下看,看下遞增和遞減時使用:
void RefCountedThreadSafeBase::AddRef() const {
#ifndef NDEBUG
DCHECK(!in_dtor_);
#endif
AtomicRefCountInc(&ref_count_);
}
bool RefCountedThreadSafeBase::Release() const {
#ifndef NDEBUG
DCHECK(!in_dtor_);
DCHECK(!AtomicRefCountIsZero(&ref_count_));
#endif
if (!AtomicRefCountDec(&ref_count_)) {
#ifndef NDEBUG
in_dtor_ = true;
#endif
return true;
}
return false;
}
主要是AtomicRefCountInc和AtomicRefCountDec函數來控制,先來看下AtomicRefCountInc函數的定義:
// Increment a reference count by 1.
inline void AtomicRefCountInc(volatile AtomicRefCount *ptr) {
base::AtomicRefCountIncN(ptr, 1);
}
// Increment a reference count by "increment", which must exceed 0.
inline void AtomicRefCountIncN(volatile AtomicRefCount *ptr,
AtomicRefCount increment) {
subtle::NoBarrier_AtomicIncrement(ptr, increment);
}
inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr,
Atomic32 increment) {
return Barrier_AtomicIncrement(ptr, increment);
}
inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr,
Atomic32 increment) {
return InterlockedExchangeAdd(
reinterpret_cast<volatile LONG*>(ptr),
static_cast<LONG>(increment)) + increment;
}
我們看到AtomicRefCountInc->AtomicRefCountIncN->NoBarrier_AtomicIncrement->Barrier_AtomicIncrement這樣的調用鏈,使用volatile類型的指針,可以避免指向的值被編譯器優化,關於volatile的用法可以參照:volatile的解讀
最終是由InterlockedExchangeAdd函數來維持遞增時的線程安全,InterlockedExchangeAdd時windows的api(因爲我這邊用windows來看的源碼,只是說下windows的),這個函數用於對一個32位數值執行加法的原子操作,具體的底層原理我們這裏就不說了。這裏還有一個地方需要注意的NoBarrier_AtomicIncrement調用了Barrier_AtomicIncrement函數,是非內存屏障的函數調用了內存屏障的函數(關於內存屏障我在volatile的解讀也有說),因爲對於遞增和遞減這樣的函數其實我們不需要要求他是內存屏障的,所以我們會調用NoBarrier_AtomicIncrement,然後在windows下要保證遞增時原子性的,即使用InterlockedExchangeAdd函數,這個函數又是內存屏障形式的,所以有了這樣的調用。遞減函數同理,只是判斷下值是否是爲0。
繼續看下HasOneRef函數,用來判斷是不是引用計數的值爲1:
bool RefCountedThreadSafeBase::HasOneRef() const {
return AtomicRefCountIsOne(
&const_cast<RefCountedThreadSafeBase*>(this)->ref_count_);
}
使用AtomicRefCountIsOne函數來判斷,我們看下實現:
inline bool AtomicRefCountIsOne(volatile AtomicRefCount *ptr) {
bool res = (subtle::Acquire_Load(ptr) == 1);
if (res) {
ANNOTATE_HAPPENS_AFTER(ptr);
}
return res;
}
inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) {
Atomic32 value = *ptr;
return value;
}
同樣使用volatile指針來取ref_count_的值,簡單解釋下這個流程,由於我們上邊講的遞增和遞減時首先更新ref_count_的地址的指向的值,這裏可能有點繞,可能在想ref_count_的地址的指向的值不就是ref_count_,其實不是的,因爲ref_count_的地址的指向的值是內存中的值,而ref_count_這個可能緩存中的值,或者寄存器中的值,然後我們再來理解這個函數實現,是不是就簡單多了,其實就是從內存中取指來判斷是不是爲1。
我們會用到的類
上邊我們講了base類,base類是源碼裏邊需要用到的類,即爲了做一些實現用到的基礎類,接下來我們來看下我們如果想要使用scoped_refptr這個智能指針,我們的類需要繼承的一些類。
RefCounted
首先是簡單RefCounted類,我們看下他的實現:
template <class T>
class RefCounted : public subtle::RefCountedBase {
public:
RefCounted() {}
void AddRef() const {
subtle::RefCountedBase::AddRef();
}
void Release() const {
if (subtle::RefCountedBase::Release()) {
delete static_cast<const T*>(this);
}
}
protected:
~RefCounted() {}
private:
DISALLOW_COPY_AND_ASSIGN(RefCounted<T>);
};
這個函數比較簡單,繼承了RefCountedBase類,重新實現AddRef和Release函數,在Release裏如果引用計數爲0時,就析構掉自己。
然後看下這個函數的使用方式,看下注釋裏說的:
// A base class for reference counted classes. Otherwise, known as a cheap
// knock-off of WebKit's RefCounted<T> class. To use this guy just extend your
// class from it like so:
//
// class MyFoo : public base::RefCounted<MyFoo> {
// ...
// private:
// friend class base::RefCounted<MyFoo>;
// ~MyFoo();
// };
//
// You should always make your destructor private, to avoid any code deleting
// the object accidently while there are references to it.
註釋裏有說明我們如何使用這個類,因爲這個類其實是個模板類,我們繼承他的同時,模板參數指定爲我們自己的類。然後需要把我們自己的類的析構函數設置爲私有的,因爲我們使用了智能指針,析構應該是交由智能指針,不能在外部析構。然後因爲析構函數是私有的了,這裏的做法是將父類設置成我們類的友元,這樣父類便能夠析構我們類的對象了。
RefCountedThreadSafe
接下來我們看我們需要繼承使用線程安全的父類RefCountedThreadSafe,再這之前我們先看下DefaultRefCountedThreadSafeTraits這個類的源碼:
// Default traits for RefCountedThreadSafe<T>. Deletes the object when its ref
// count reaches 0. Overload to delete it on a different thread etc.
template<typename T>
struct DefaultRefCountedThreadSafeTraits {
static void Destruct(const T* x) {
// Delete through RefCountedThreadSafe to make child classes only need to be
// friend with RefCountedThreadSafe instead of this struct, which is an
// implementation detail.
RefCountedThreadSafe<T,
DefaultRefCountedThreadSafeTraits>::DeleteInternal(x);
}
};
首先這個類是個模板類,這個類的主要作用便是Destruct函數,析構傳入模板參數的類的對象,通過調用RefCountedThreadSafe的DeleteInternal函數來實現。我們先看下RefCountedThreadSafe實現再細講下:
template <class T, typename Traits = DefaultRefCountedThreadSafeTraits<T> >
class RefCountedThreadSafe : public subtle::RefCountedThreadSafeBase {
public:
RefCountedThreadSafe() {}
void AddRef() const {
subtle::RefCountedThreadSafeBase::AddRef();
}
void Release() const {
if (subtle::RefCountedThreadSafeBase::Release()) {
Traits::Destruct(static_cast<const T*>(this));
}
}
protected:
~RefCountedThreadSafe() {}
private:
friend struct DefaultRefCountedThreadSafeTraits<T>;
static void DeleteInternal(const T* x) { delete x; }
DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafe);
};
這個和RefCounted很像,同樣我們使用的時候需要設置這個類是我們自己類的父類及友元類,自己的析構函數是私有的。但是這個類特殊的地方在於使用Traits類來做析構,同時我們看默認的Traits類析構是還是使用RefCountedThreadSafe類的函數,這樣看是不是多此一舉了,其實不是我們仔細看下DefaultRefCountedThreadSafeTraits的註釋有說在另外的線程析構時會用到你自己定義的Traits類,這就說明這個用法,即有些時候我們需要析構對象在別的線程,然後這就給我們提供很好的方式來解決。
RefCountedData
我們首先看下源碼:
//
// A thread-safe wrapper for some piece of data so we can place other
// things in scoped_refptrs<>.
//
template<typename T>
class RefCountedData
: public base::RefCountedThreadSafe< base::RefCountedData<T> > {
public:
RefCountedData() : data() {}
RefCountedData(const T& in_value) : data(in_value) {}
T data;
private:
friend class base::RefCountedThreadSafe<base::RefCountedData<T> >;
~RefCountedData() {}
};
RefCountedData繼承了RefCountedThreadSafe類,看起來和我們自定義類有幾分相似,然定義了一個data用來存放數據,其實個人認爲這個類是可以直接給我們用的,而不是需要去繼承的,我們使用自定義類的對象封裝在scoped_refptr時,可以使用RefCountedThreadSafe或者RefCounted,如果我們類似想要封裝原始類型(int,bool等)的智能指針,就可以使用RefCountedData< int >或者RefCountedData< bool >,當然也不是必須如此,可以看下base裏邊怎麼使用的:
class DeletionHelper : public base::RefCountedThreadSafe<DeletionHelper> {
public:
explicit DeletionHelper(
const scoped_refptr<base::RefCountedData<bool> >& deleted_flag)
: deleted_flag_(deleted_flag) {
}
private:
friend class base::RefCountedThreadSafe<DeletionHelper>;
virtual ~DeletionHelper() { deleted_flag_->data = true; }
const scoped_refptr<base::RefCountedData<bool> > deleted_flag_;
DISALLOW_COPY_AND_ASSIGN(DeletionHelper);
};
這個其實使用RefCountedData這個類,就是類似封裝了bool的智能指針。
scoped_refptr
接下來開始我們的重頭戲scoped_refptr,首先我們把代碼粘出來,然後在仔細分析下他的內容及用法
template <class T>
class scoped_refptr {
public:
typedef T element_type;
scoped_refptr() : ptr_(NULL) {
}
scoped_refptr(T* p) : ptr_(p) {
if (ptr_)
ptr_->AddRef();
}
scoped_refptr(const scoped_refptr<T>& r) : ptr_(r.ptr_) {
if (ptr_)
ptr_->AddRef();
}
template <typename U>
scoped_refptr(const scoped_refptr<U>& r) : ptr_(r.get()) {
if (ptr_)
ptr_->AddRef();
}
~scoped_refptr() {
if (ptr_)
ptr_->Release();
}
T* get() const { return ptr_; }
operator T*() const { return ptr_; }
T* operator->() const {
assert(ptr_ != NULL);
return ptr_;
}
scoped_refptr<T>& operator=(T* p) {
// AddRef first so that self assignment should work
if (p)
p->AddRef();
T* old_ptr = ptr_;
ptr_ = p;
if (old_ptr)
old_ptr->Release();
return *this;
}
scoped_refptr<T>& operator=(const scoped_refptr<T>& r) {
return *this = r.ptr_;
}
template <typename U>
scoped_refptr<T>& operator=(const scoped_refptr<U>& r) {
return *this = r.get();
}
void swap(T** pp) {
T* p = ptr_;
ptr_ = *pp;
*pp = p;
}
void swap(scoped_refptr<T>& r) {
swap(&r.ptr_);
}
protected:
T* ptr_;
};
代碼很少,應該也比較好理解,scoped_refptr是一個模板類,自然模板參數就是我們自定義的類,或者我們使用的RefCountedData類,成員ptr_就是指向我們自定義類的對象。
- 默認構造函數初始化ptr_爲空。
- T*參數的構造函數,參數賦值給ptr_,同時調用ptr_的AddRef函數,上邊也說了我們的類會繼承RefCountedThreadSafe和RefCounted,這些類裏邊已經幫我們管理了引用計數,定義了AddRef和Release函數。
- 拷貝構造函數中獲得ptr_的值,然後增加引用計數
- 在析構函數中遞減引用計數
- 重載箭頭函數
- 重載賦值運算符,將原來的ptr_遞減引用計數,將要賦值的ptr_遞增引用計數,即ptr1 = ptr2時,因爲ptr1是要將之前封裝的對象的引用計數減1,因爲他不再擁有這個對象了,然後因爲ptr1和ptr2同時擁有一個對象,這個引用計數加1。
實例
基本上所有的類我們都講了一遍,這裏我們總結下,如果我們想要使用scoped_refptr這個智能指針,要麼直接使用RefCountedData來封裝我們的數據,要麼就是我們自定義類,繼承RefCounted或者RefCountedThreadSafe(需要線程安全),然後scoped_refptr的模板 參數是我們的自定義類或者RefCountedData,然後就使用scoped_refptr來進行我們的目的。我們看下注釋裏的例子:
// class MyFoo : public RefCounted<MyFoo> {
// ...
// };
//
// void some_function() {
// scoped_refptr<MyFoo> foo = new MyFoo();
// foo->Method(param);
// // |foo| is released when this function returns
// }
//
// void some_other_function() {
// scoped_refptr<MyFoo> foo = new MyFoo();
// ...
// foo = NULL; // explicitly releases |foo|
// ...
// if (foo)
// foo->Method(param);
// }
MyFoo 是我們的自定義類,scoped_refptr對象調用MyFoo類的函數。
// scoped_refptr<MyFoo> a = new MyFoo();
// scoped_refptr<MyFoo> b;
//
// b.swap(a);
置換兩個指針的值
// scoped_refptr<MyFoo> a = new MyFoo();
// scoped_refptr<MyFoo> b;
//
// b = a;
兩個智能指針指向相同的資源
注
本篇文章到這裏就講完了,其實這個源碼是較老版本的base了,其實原理其實都是一樣,一方面爲了自己學習,一方面記錄下來,和大家交流下。scoped_refptr其實是侵入式的std::shared_ptr。如果看較舊一點的代碼我們發現base庫裏有scoped_ptr,這其實就是std::unique_ptr,新版中已經廢棄了,我說這個是避免大家搞混了,歡迎交流。