webrtc中的引用计数系统

158 阅读6分钟

webrtc中的引用计数系统主要由四个类一起构建:

RefCounter,
RefCountInterface,
RefCountedObject,
scoped_refptr

1. RefCounter:

既然是引用计数系统,那就一定需要一个计数器,用于保存并更新引用计数。
webrtc中使用了C++的原子变量(std::atomic< int>),使用 fetch_add(1)fetch_sub(1) 做+1和-1操作。
RefCounter类的源码如下:

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

2. RefCountInterface:

RefCountInterface是一个抽象接口类,提供AddRef()和Release()两个接口, 供需要使用引用计数的类来继承。

注意:webrtc中一个类如果要使用引用计数系统,则必须继承RefCountInterface(同时,这样会使得继承后的子类也成为一个抽象类,无法实例化))。

RefCountInterface类的源码如下:

class RefCountInterface {
public:
  virtual void AddRef() const = 0;
  virtual RefCountReleaseStatus Release() const = 0;
protected:
  virtual ~RefCountInterface() {}
};

3. RefCountedObject:

RefCountedObject类真正实现了引用计数增减方法(实现了AddRef()和Release()函数),它有下面几个特性:

  1. 持有一个RefCounter成员;
  2. 实现AddRef()和Release()函数,通过调用RefCounter成员的IncRef()和DecRef()接口完成更新引用计数的动作;
  3. 是一个模板类,同时又继承模板参数,是模板参数的子类;
  4. 使用std::forward完美转发提高效率。

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

小结: 实现一个带引用计数的类

至此,有了上面的 RefCounter、RefCountInterface、RefCountedObject 三个类,我们就可以实现一个带引用计数的类,例如 VideoTrack类:

// 通过继承RefCounterInterface基类,VideoTrack中带有AddRef()和Release()虚函数
class VideoTrack : public RefCounterInterface {
public:
    VideoTrack(int id) : track_id(id) {}
private:
    int track_id;
};

int id = 5;
// 使用VideoTrack实例化RefCountedObject模板类
RefCountedObject<VideoTrack> *instance = new RefCountedObject<VideoTrack>(id);

// RefCountedObject<VideoTrack>对象带有引用计数,支持手动的增减计数操作:
instance->AddRef();
instance->Release();

继承关系如下: RefCountedObject --> VideoTrack --> RefCounterInterface

关键点:

Q: 为什么不把AddRef()和Release()接口的实现直接放在VideoTrack类中,而要使用RefCountedObject这样一个奇怪的模板类作为VideoTrack的子类呢?
A: 因为如果没有RefCountedObject这个模板类,那么每一个想要使用引用计数系统的类都必须自行实现AddRef()和Release()接口,例如后面再有AudioTrack、PictureTrack等等类似的类,都要重复开发这两个接口函数。
这会带来很多重复代码,降低开发效率,并提高使用难度。
有了RefCountedObject这个模板类,只需要实现VideoTrack自身的业务函数,然后在需要创建VideoTrack实例时,按照格式 new RefCountedObject<VideoTrack>() 创建对象即可。

4. scoped_refptr:

scoped_refptr类的作用是真正实现 “智能指针”,在前面的RefCountedObject类型对象只是一个带有引用计数的类,为了避免直接对RefCountedObject对象做手动增减操作(这样做并不“智能”,可能会忘记调用AddRef()或Release()而引发内存泄漏,或重复Release()导致coredump),需要使用 scopted_refptr 来根据类的构造和析构动作,自动做增减操作。

scoped_refptr的使用方式如下:

scoped_refptr<VideoTrack> track = new RefCountedObject<VideoTrack>(id);

scoped_refptr的源码如下:

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<T>的release()
  //     方法,将原来的scoped_refptr<T>对象内部引用计数指针置空,新的scoped_refptr<T>
  //     对象来保存引用计数对象,以达到转移的目的。   
  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_;
};

至此,有了上述四个类,就实现了webrtc中的引用计数系统。