WebRTC中自己实现了一套引用计数系统,在其基础库模块rtc_base/refcount中提供了相关实现,如下图所示:
主要由四个类RefCountInterface、RefCounter、RefCountedObject、scoped_refptr一起构建起WebRTC中的引用计数系统。
RefCountInterface是一个抽象接口类,位于rtc_base/ref_count.h目录下。源码如下:
enum class RefCountReleaseStatus { kDroppedLastRef, kOtherRefsRemained };
// Interfaces where refcounting is part of the public api should
// inherit this abstract interface. The implementation of these
// methods is usually provided by the RefCountedObject template class,
// applied as a leaf in the inheritance tree.
class RefCountInterface {
public:
virtual void AddRef() const = 0;
virtual RefCountReleaseStatus Release() const = 0;
// Non-public destructor, because Release() has exclusive responsibility for
// destroying the object.
protected:
virtual ~RefCountInterface() {}
};
正如该类的英文注释所说那样:需要使用引用计数的公共接口类需要继承RefCountInterface这个抽象接口。但公共接口类并不直接实现RefCountInterface的纯虚方法AddRef() && Release()。这两个方法由后文的模板对象RefCountedObject对象来提供具体的实现。
比如WebRTC中的类RTCStatsReport,它继承了RefCountInterface,但是RTCStatsReport类并没有实现AddRef()和Release()这两个纯虚方法,也即RTCStatsReport仍然是个抽象类。而使用该类时,不能直接new RTCStatsReport(因为它是抽象类)。 一般会如此使用:
rtc::scoped_refptr<RTCStatsReport> statsReport = new RefCountedObject<RTCStatsReport>(...);
RefCountedObject
如此设计后,这三个类的继承关系就变为:(RefCountInterface就是继承链上的叶子节点)
RefCounter是实现引用计数的核心类,代码位于rtc_base/ref_counter.h文件中。该类的对象用来保存被引用对象的计数值。
class RefCounter {
public:
explicit RefCounter(int ref_count) : ref_count_(ref_count) {}
RefCounter() = delete;
void IncRef() {
ref_count_.fetch_add(1, std::memory_order_relaxed);
}
rtc::RefCountReleaseStatus DecRef() {
int ref_count_after_subtract =
ref_count_.fetch_sub(1, std::memory_order_acq_rel) - 1;
return ref_count_after_subtract == 0
? rtc::RefCountReleaseStatus::kDroppedLastRef
: rtc::RefCountReleaseStatus::kOtherRefsRemained;
}
bool HasOneRef() const {
return ref_count_.load(std::memory_order_acquire) == 1;
}
private:
std::atomic<int> ref_count_;
};
正如上述源码可知,实际上该类使用了c++标准库的原子操作类std::atomic
RefCountedObject
该类的源码如下:
template <class T>
class RefCountedObject : public T {
public:
RefCountedObject() {}
template <class P0>
explicit RefCountedObject(P0&& p0) : T(std::forward<P0>(p0)) {}
template <class P0, class P1, class... Args>
RefCountedObject(P0&& p0, P1&& p1, Args&&... args)
: T(std::forward<P0>(p0),
std::forward<P1>(p1),
std::forward<Args>(args)...) {}
virtual void AddRef() const { ref_count_.IncRef(); }
virtual RefCountReleaseStatus Release() const {
const auto status = ref_count_.DecRef();
if (status == RefCountReleaseStatus::kDroppedLastRef) {
delete this;
}
return status;
}
virtual bool HasOneRef() const { return ref_count_.HasOneRef(); }
protected:
virtual ~RefCountedObject() {}
mutable webrtc::webrtc_impl::RefCounter ref_count_{0};
RTC_DISALLOW_COPY_AND_ASSIGN(RefCountedObject);
};
代码实现简单,也容易理解,有这么几点需要重点强调下:
PS1: 完美转发std:forward作用:在模板内,如果我们需要将一组参数原封不动的传递给另外一个参数,在没有完美转发的情况下,考虑参数会有多种类型的重载,因此在没有完美转发的情况下,重载函数个数将会达到2^n个,多么庞大的人工量。当使用std::forward辅以模板参数推导规则以保持参数属性不变,实现完美转发节省了大量的工作。std::forward分析可见:详解C++11中移动语义(std::move)和完美转发(std::forward)
PS2: RTC_DISALLOW_COPY_AND_ASSIGN(RefCountedObject) 宏展开后相当于两个语句:即移除了默认的拷贝构造和赋值运算符。
RefCountedObject(const RefCountedObject&) = delete;
RefCountedObject& operator=(const RefCountedObject&) = delete
rtc::scoped_refptr
使用智能指针,避免直接使用引用计数对象、手动调用引用计数对象的AddRef()、Release()。可以有效防止忘记Release一个对象所引发的内存泄漏。
template <class T>
class scoped_refptr {
public:
typedef T element_type;
// 1. 6个构造函数
// 1.1 默认构造,传入空的引用计数对象
scoped_refptr() : ptr_(nullptr) {}
// 1.2 构造,传入引用计数对象p的指针,调用其AddRef()方法,引用计数+1。
scoped_refptr(T* p) : ptr_(p) { // NOLINT(runtime/explicit)
if (ptr_)
ptr_->AddRef();
}
// 1.3 拷贝构造,对应的引用计数+1,调用引用计数对象的AddRef()方法。
scoped_refptr(const scoped_refptr<T>& r) : ptr_(r.ptr_) {
if (ptr_)
ptr_->AddRef();
}
// 1.4 拷贝构造,U必须是T的子类,同1.3
template <typename U>
scoped_refptr(const scoped_refptr<U>& r) : ptr_(r.get()) {
if (ptr_)
ptr_->AddRef();
}
// 1.5 移动构造(右值引用),表示对象的转移,亦即使用同一个对象,因此,需要保持
// 对引用计数对象的引用次数不变。所以,此处调用scoped_refptr的release()
// 方法,将原来的scoped_refptr对象内部引用计数指针置空,新的scoped_refptr
// 对象来保存引用计数对象,以达到转移的目的。
scoped_refptr(scoped_refptr<T>&& r) noexcept : ptr_(r.release()) {}
// 1.6 移动构造,U必须是T的子类,同1.5
template <typename U>
scoped_refptr(scoped_refptr<U>&& r) noexcept : ptr_(r.release()) {}
// 2 析构函数,调用引用计数对象Release(),引用计数-1
~scoped_refptr() {
if (ptr_)
ptr_->Release();
}
// 3. get、release、()、->()方法
T* get() const { return ptr_; }
operator T*() const { return ptr_; }
T* operator->() const { return ptr_; }
T* release() {
T* retVal = ptr_;
ptr_ = nullptr;
return retVal;
}
// 4. 重载赋值运算符
// 4.1 赋值新的引用计数对象的指针,新引用计数对象的引用计数+1,原来的-1
scoped_refptr<T>& operator=(T* p) {
// AddRef first so that self assignment should work
// 先增加引用,再减小原来的引用,这样可以使得自赋值能正常工作
if (p)
p->AddRef();
if (ptr_)
ptr_->Release();
ptr_ = p;
return *this;
}
// 4.2 赋值智能指针,新引用计数对象的引用计数+1,原来的-1
scoped_refptr<T>& operator=(const scoped_refptr<T>& r) {
// 取出智能指针的内部引用计数的指针,利用4.1的功能实现赋值
return *this = r.ptr_;
}
// 4.3 赋值T的子类U的智能指针,新引用计数对象的引用计数+1,原来的-1
// 具体使用过程中,这个赋值方法用得最多。
template <typename U>
scoped_refptr<T>& operator=(const scoped_refptr<U>& r) {
// 使用get()方法取出智能指针的内部引用计数的指针,利用4.1的功能实现赋值
return *this = r.get();
}
// 4.4 移动赋值右值智能指针,新引用计数对象的引用计数不变,原来引用计数对象的引用计数不变
scoped_refptr<T>& operator=(scoped_refptr<T>&& r) noexcept {
// 使用移动语义std::move + 移动构造 + swap进行引用计数对象的地址交换
scoped_refptr<T>(std::move(r)).swap(*this);
return *this;
}
// 4.5 移动赋值T的子类U的右值智能指针,新引用计数对象的引用计数不变,原来引用计数对象的引用计数不变
template <typename U>
scoped_refptr<T>& operator=(scoped_refptr<U>&& r) noexcept {
// 使用移动语义std::move + 移动构造 + swap进行引用计数对象的地址交换
scoped_refptr<T>(std::move(r)).swap(*this);
return *this;
}
// 5 地址交换函数
// 5.1 引用计数对象的地址交换
void swap(T** pp) noexcept {
T* p = ptr_;
ptr_ = *pp;
*pp = p;
}
// 5.2 智能智能内部的引用计数对象的地址交换,利用5.1
void swap(scoped_refptr<T>& r) noexcept { swap(&r.ptr_); }
protected:
T* ptr_;
};
rtc::scoped_refptr
在此,举两个WebRTC源码中介绍rtc::scoped_refptr
源码如下:
class MyFoo : public RefCountInterface {
...
};
void some_function() {
scoped_refptr<MyFoo> foo = new RefCountedObject<MyFoo>();
foo->Method(param);
//|foo| is released when this function returns
}
void some_other_function() {
scoped_refptr<MyFoo> foo = new RefCountedObject<MyFoo>();
...
foo = nullptr; // explicitly releases |foo|
...
if (foo)
foo->Method(param);
}
上面的示例演示了scoped_refptr
续 6.1
{
scoped_refptr<MyFoo> a = new RefCountedObject<MyFoo>();
scoped_refptr<MyFoo> b;
b.swap(a);
// now, |b| references the MyFoo object, and |a| references null.
}
{
scoped_refptr<MyFoo> a = new RefCountedObject<MyFoo>();
scoped_refptr<MyFoo> b;
b = a;
// now, |a| and |b| each own a reference to the same MyFoo object.
}
第一个{…}演示了两个智能指针对象之间如何交换底层保存的引用计数对象。通过swap(),最终b引用了a之间保存的引用计数对象,而a则引用nullptr,因为b之前就是引用nullptr。
第二个{…}演示了如何让智能指针b和a引用同一个引用计数对象,简单的赋值即可。上述示例将使得b也引用了之前a保存的引用计数对象,引用计数为2。若是语句b = a 改为 a = b,那么a和b都将引用空对象nullptr,之前a保存的引用计数对象RefCountedObject
行文至此,大致对WebRTC源码中引用计数系统实现以及使用方式做了简要的介绍,现在回顾下要点