WebRTC基本类 - 智能指针(RefCountedObject和scoped_refptr)

一、前言:

C++11之前我们程序员都是按照new/delete这么去管理堆对象生命周期的,可是如果new了忘记delete就会造成内存泄漏,我们不能光用勤奋去保证质量,人和动物的本质区别就是使用工具,于是乎智能指针就问世了。可是WebRtc当中不管使用了C++本身的智能指针,还有自己一套办法,而且随处可见,值得我们研究下。

二、RefCountedObject:

WebRTC基本类 - 智能指针(RefCountedObject和scoped_refptr)

RefCountedObject在WebRtc到处都在使用,从名字看就是“带引用计数的对象”,这个类模板提供了通过引用计数来管理对象生命周期的机制,确保正确的内存管理和线程安全。

1、定义:

路径:rtc_base\ref_counted_object.h

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

  // Return whether the reference count is one. If the reference count is used
  // in the conventional way, a reference count of 1 implies that the current
  // thread owns the reference and no other thread shares it. This call
  // performs the test for a reference count of one, and performs the memory
  // barrier needed for the owning thread to act on the object, knowing that it
  // has exclusive access to the object.
  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);
};

构造函数重载

  • 该类提供了多个构造函数重载,以便使用不同数量的参数初始化对象。
  • 构造函数使用完美转发将参数传递给基类构造函数。

AddRef 方法

  • AddRef() 方法用于增加引用计数。标记为 const 表示它不会修改对象的逻辑状态。

Release 方法

  • Release() 方法减少引用计数,并在计数达到零时删除对象(表示这是最后一个引用)。
  • 它返回一个 RefCountReleaseStatus 枚举值,表示释放操作后的状态。

HasOneRef 方法

  • HasOneRef() 方法用于检查引用计数是否为一,表示当前线程拥有对象的独占所有权。该方法确保拥有线程对对象具有独占访问权。

析构函数

  • 类具有受保护的虚拟析构函数,以确保在删除对象时正确清理。

成员变量

  • ref_count_webrtc::webrtc_impl::RefCounter 类的一个实例,用于管理引用计数。标记为 mutable 允许在 const 成员函数中进行修改。

RTC_DISALLOW_COPY_AND_ASSIGN

  • 这可能定义了一个宏,禁止复制构造和复制赋值,强制通过引用计数管理对象。

2、实例:

文件路径:.\audio\audio_state.cc

rtc::scoped_refptr<AudioState> AudioState::Create(
    const AudioState::Config& config) {
  return new rtc::RefCountedObject<internal::AudioState>(config);
}

带入模板展开:

  • 使用internal::AudioState代替T;
  • 使用AudioState::Config代替P0;
class RefCountedObject : public internal::AudioState {
 public:
  explicit RefCountedObject(AudioState::Config&& p0) : internal::AudioState(std::forward<AudioState::Config>(p0)) {}
}

这样,就相当于:

new internal::AudioState(config)

那么可以看到,我们使用了RefCountedObject,对象是有引用计数了,可是没看到有人调用AddRef增加引用计数;同样没有看到Release去减少引用计数啊。难道让我们自己手动调用??那我们肯定不干了,我们自己能想起来释放,还要这个引用计数做什么呢?看看rtc::scoped_refptr AudioState::Create()返回值,这就是答案,webrtc的智能指针rtc::scoped_refptr。

三、rtc::scoped_refptr:

scoped_refptr 类的设计目的是为了在 WebRTC 中管理引用计数对象的生命周期,确保正确地增加和减少对象的引用计数,避免内存泄漏和悬空指针的问题。

1、定义:

template <class T>
class scoped_refptr {
 public:
  typedef T element_type;
  /* 重载了多个构造函数 */
  //  默认构造,传入空的引用计数对象
  scoped_refptr() : ptr_(nullptr) {}
  // 传入了一个指针对象p,会自动调用p的AddRef增加引用计数
  scoped_refptr(T* p) : ptr_(p) {  
    if (ptr_)
      ptr_->AddRef();
  }
  // 拷贝构造,会自动调用p的AddRef增加引用计数
  scoped_refptr(const scoped_refptr<T>& r) : ptr_(r.ptr_) { 
    if (ptr_)
      ptr_->AddRef();
  }
  // 拷贝构造,U是T子类
  template <typename U>
  scoped_refptr(const scoped_refptr<U>& r) : ptr_(r.get()) { 
    if (ptr_)
      ptr_->AddRef();
  }

  // Move constructors. 移动构造, 由于是右值引用,原对象引用置空
  scoped_refptr(scoped_refptr<T>&& r) noexcept : ptr_(r.release()) {}
  // 移动构造, U是T的子类
  template <typename U>
  scoped_refptr(scoped_refptr<U>&& r) noexcept : ptr_(r.release()) {}

  /* 析构函数 */
  // 当 scoped_refptr 对象超出作用域时,会自动调用析构函数来释放资源
  ~scoped_refptr() {
    if (ptr_)
      ptr_->Release();
  }
  /* 指针引用方式 */
  // 返回被管理对象的原始指针
  T* get() const { return ptr_; } 
  // 对象隐式转换为被管理对象的原始指针类型 T*,比如ptr1(ptr)
  operator T*() const { return ptr_; } 
  // 重载了解引用运算符 *,允许直接通过 * 操作符访问 scoped_refptr 对象所管理的对象
  T& operator*() const { return *ptr_; } 
  // 重载了箭头运算符 ->,使得可以通过 -> 运算符来访问 scoped_refptr 对象所管理的对象的成员
  T* operator->() const { return ptr_; } 

  // Returns the (possibly null) raw pointer, and makes the scoped_refptr hold a
  // null pointer, all without touching the reference count of the underlying
  // pointed-to object. The object is still reference counted, and the caller of
  // release() is now the proud owner of one reference, so it is responsible for
  // calling Release() once on the object when no longer using it.
  // 返回被管理对象的原始指针,并将 scoped_refptr 对象置空,需要手动管理对象的生命周期
  T* release() {
    T* retVal = ptr_;
    ptr_ = nullptr;
    return retVal;
  }
  /* 重载赋值运算符 */
  // 赋值运算符(新对象引用计数+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;
  }
  // 赋值给智能指针(新对象引用计数+1,原对象-1)
  scoped_refptr<T>& operator=(const scoped_refptr<T>& r) {
    return *this = r.ptr_;
  }
  // 赋值T的子类U的智能指针(新对象引用计数+1,原对象-1)
  template <typename U>
  scoped_refptr<T>& operator=(const scoped_refptr<U>& r) {
    return *this = r.get();
  }
  // 移动赋值右值智能指针(新对象和原对象引用计数都不变)
  scoped_refptr<T>& operator=(scoped_refptr<T>&& r) noexcept {
    scoped_refptr<T>(std::move(r)).swap(*this);
    return *this;
  }
  // 移动赋值T的子类U的右值智能指针(新对象和原对象引用计数都不变)
  template <typename U>
  scoped_refptr<T>& operator=(scoped_refptr<U>&& r) noexcept {
    scoped_refptr<T>(std::move(r)).swap(*this);
    return *this;
  }
  // 交换两个指针所指的对象
  void swap(T** pp) noexcept {
    T* p = ptr_;
    ptr_ = *pp;
    *pp = p;
  }
  // 交换两个引用对象的地址
  void swap(scoped_refptr<T>& r) noexcept { swap(&r.ptr_); }

 protected:
  T* ptr_;
};

可以看到构造函数将p赋值给了成员变量ptr_;然后就是重载了各种运算符,覆盖了“赋值”“赋值构造”“交换”等各种场景。这样RefCountedObject内部有自己的引用计数,和操作引用计数的函数,scoped_refptr则根据各种使用场景来管理RefCountedObject生命周期。

2、实例:

我们先看个例子:

lass MyRefCountedObject : public RefCountedObject {
public:
    MyRefCountedObject() {
        std::cout << "MyRefCountedObject created" << std::endl;
    }

    ~MyRefCountedObject() {
        std::cout << "MyRefCountedObject destroyed" << std::endl;
    }

    void DoSomething() override {
        std::cout << "Doing something in MyRefCountedObject" << std::endl;
    }
};

int main() {
    scoped_refptr<MyRefCountedObject> ptr1(new MyRefCountedObject());
    scoped_refptr<MyRefCountedObject> ptr2(ptr1);

    ptr1->DoSomething();
    ptr2->DoSomething();

    // 退出作用域时,ptr1 和 ptr2 的析构函数会自动调用,引用计数会减少
    return 0;
}

这样在ptr1创建时候增加一个引用计数, ptr2(ptr1)时候由于又引用一次,计数增加引用计数。当退出作用域时候ptr1和ptr2栈变量生命周期都已经结束,因此,会调用析构函数来delete对象。

3、解释下swap方法应用场景:

解释下 void swap(T** pp) 这种情况:

#include 

template <class T>
class scoped_refptr {
public:
    scoped_refptr(T* p) : ptr_(p) {}

    void swap(T** pp) noexcept {
        T* p = ptr_;
        ptr_ = *pp;
        *pp = p;
    }

    T* get() const { return ptr_; }

private:
    T* ptr_;
};

class MyClass {
public:
    void display() {
        std::cout << "Hello from MyClass!" << std::endl;
    }
};

int main() {
    MyClass* obj1 = new MyClass();
    MyClass* obj2 = new MyClass();

    scoped_refptr<MyClass> ptr1(obj1);
    scoped_refptr<MyClass> ptr2(obj2);

    std::cout << "Before swap:" << std::endl;
    ptr1.get()->display();
    ptr2.get()->display();

    // 调用 swap 方法交换两个指针的指向
    ptr1.swap(&ptr2);

    std::cout << "\nAfter swap:" << std::endl;
    ptr1.get()->display();
    ptr2.get()->display();

    delete obj1;
    delete obj2;

    return 0;
}

在这个示例中,我们创建了两个 MyClass 类对象 obj1obj2,然后分别使用 scoped_refptrptr1ptr2 来管理这两个对象的指针。

通过调用 ptr1.swap(&ptr2);,我们交换了 ptr1ptr2 所指向的对象。在交换后,ptr1 指向了原来 ptr2 所管理的对象,而 ptr2 则指向了原来 ptr1 所管理的对象。

四、总结:

rtc_base中的RefCountInterface、RefCounter、RefCountedObject都是有自身引用计数的,我们解释了RefCountedObject和scoped_refptr如何进行配合,也就理解了webrtc里面如何进行智能管理对象生命周期。

欢迎关注vx公众号:

版权声明:如无特殊标注,文章均来自网络,本站编辑整理,转载时请以链接形式注明文章出处,请自行分辨。

本文链接:https://www.shbk5.com/dnsj/74860.html