热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

WebRTC源码分析:引用计数系统

WebRTC源

引言

WebRTC中自己实现了一套引用计数系统,在其基础库模块rtc_base/refcount中提供了相关实现,如下图所示:

主要由四个类RefCountInterface、RefCounter、RefCountedObject、scoped_refptr一起构建起WebRTC中的引用计数系统。

2. RefCountInterface——引用计数抽象接口

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 statsReport = new RefCountedObject(...);

RefCountedObject这个类继承了RTCStatsReport,并为AddRef()和Release()两个纯虚方法提供了实现。

本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击领取↓↓

如此设计后,这三个类的继承关系就变为:(RefCountInterface就是继承链上的叶子节点)

  • AddRef():将对象的引用计数线+1
  • Release():将对象的引用计数-1,若引用计数为0,即kDroppedLastRef。那么对象将在Release()中被delete。
RefCounter——引用计数

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

正如上述源码可知,实际上该类使用了c++标准库的原子操作类std::atomic来保存引用计数:

  • 利用std::atomica类的 fetch_add() 方法来实现+1的操作函数IncRef();
  • 利用std::atomic类的 fetch_sub() 方法来实现-1的操作函数DecRef();
  • 利用std::atomic类的 load() 方法来实现是否刚好引用计数为1的操作函数HasOneRef();
  • fetch_add()、fetch_sub() 、load() 方法都传入了指示CPU指令中插入内存屏障,防止某种指令重排序的参数,详细解释请查看 https://zh.cppreference.com/w/cpp/atomic/memory_order
4. RefCountedObject——引用计数对象

RefCountedObject模板类是正真实现应用计数增减方法 AddRef 和 Release的地方,实质上是通过持有成员RefCounter来实现的。那么RefCountedObject——>利用RefCounter——>利用std::atomic ref_count_来进行计数的。该类的UML类图如下所示

该类的源码如下:

template class RefCountedObject : public T { public: RefCountedObject() {} template explicit RefCountedObject(P0&& p0) : T(std::forward(p0)) {} template RefCountedObject(P0&& p0, P1&& p1, Args&&... args) : T(std::forward(p0), std::forward(p1), std::forward(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); };

代码实现简单,也容易理解,有这么几点需要重点强调下:

  • RefCountedObject是继承T的,是T的子类;
  • RefCountedObject的构造函数使用了c++11中的右值引用&& 以及完美转发std::forward来提高效率;
  • Release()函数的实现,当引用计数为0时,将delete掉自己!!!
  • ref_count_成员被初始化为0,并且声明为mutable,表示该成员是可变的,将不受子类可能出现的const修饰符的影响;
  • 最后,RTC_DISALLOW_COPY_AND_ASSIGN(RefCountedObject) 对 RefCountedObject进行了修饰,表示RefCountedObject不允许赋值和拷贝。注意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 5. rtc::scoped_refptr——智能指针

本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击领取↓↓

rtc::scoped_refptr是RTC中为了使用引用计数对象的智能指针,实现位于api层的api/scoped_refptr.h中。

使用智能指针,避免直接使用引用计数对象、手动调用引用计数对象的AddRef()、Release()。可以有效防止忘记Release一个对象所引发的内存泄漏。

template 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& r) : ptr_(r.ptr_) { if (ptr_) ptr_->AddRef(); } // 1.4 拷贝构造,U必须是T的子类,同1.3 template scoped_refptr(const scoped_refptr& r) : ptr_(r.get()) { if (ptr_) ptr_->AddRef(); } // 1.5 移动构造(右值引用),表示对象的转移,亦即使用同一个对象,因此,需要保持 // 对引用计数对象的引用次数不变。所以,此处调用scoped_refptr的release() // 方法,将原来的scoped_refptr对象内部引用计数指针置空,新的scoped_refptr // 对象来保存引用计数对象,以达到转移的目的。 scoped_refptr(scoped_refptr&& r) noexcept : ptr_(r.release()) {} // 1.6 移动构造,U必须是T的子类,同1.5 template scoped_refptr(scoped_refptr&& 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& 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& operator=(const scoped_refptr& r) { // 取出智能指针的内部引用计数的指针,利用4.1的功能实现赋值 return *this = r.ptr_; } // 4.3 赋值T的子类U的智能指针,新引用计数对象的引用计数+1,原来的-1 // 具体使用过程中,这个赋值方法用得最多。 template scoped_refptr& operator=(const scoped_refptr& r) { // 使用get()方法取出智能指针的内部引用计数的指针,利用4.1的功能实现赋值 return *this = r.get(); } // 4.4 移动赋值右值智能指针,新引用计数对象的引用计数不变,原来引用计数对象的引用计数不变 scoped_refptr& operator=(scoped_refptr&& r) noexcept { // 使用移动语义std::move + 移动构造 + swap进行引用计数对象的地址交换 scoped_refptr(std::move(r)).swap(*this); return *this; } // 4.5 移动赋值T的子类U的右值智能指针,新引用计数对象的引用计数不变,原来引用计数对象的引用计数不变 template scoped_refptr& operator=(scoped_refptr&& r) noexcept { // 使用移动语义std::move + 移动构造 + swap进行引用计数对象的地址交换 scoped_refptr(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& r) noexcept { swap(&r.ptr_); } protected: T* ptr_; };

rtc::scoped_refptr的成员方法大致按照源码的注释所述,分为5类。具体就不再详述,见注释即可。需要引起重视的有如下几个观点:

  • rtc::scoped_refptr的构造 和 赋值 将调用引用计数对象的AddRef()方法,使得引用计数+1;
  • rtc::scoped_refptr的析构 将调用引用计数对象的Release()方法,使得引用计数-1,引用计数减少为0,则引用计数对象将再Release()方法中被delete。
  • rtc::scoped_refptr移动构造 和 移动赋值 将不会增加引用计数对象的计数,只会交换智能指针内部保存的引用计数对象的地址,右值引用的内部引用计数对象指针将被赋值为nullptr。
  • 小写的release()方法不可轻易调用,需要在理解其作用的条件下谨慎使用。因为,release()方法会使智能指针内部存储的ptr_为空,失去对引用计数对象的引用,而引用计数没有-1。
6. 使用举例

在此,举两个WebRTC源码中介绍rtc::scoped_refptr时的小示例:

6.1

源码如下:

class MyFoo : public RefCountInterface { ... }; void some_function() { scoped_refptr foo = new RefCountedObject(); foo->Method(param); //|foo| is released when this function returns } void some_other_function() { scoped_refptr foo = new RefCountedObject(); ... foo = nullptr; // explicitly releases |foo| ... if (foo) foo->Method(param); }

上面的示例演示了scoped_refptr如何表现得像T*

6.2

{ scoped_refptr a = new RefCountedObject(); scoped_refptr b; b.swap(a); // now, |b| references the MyFoo object, and |a| references null. } { scoped_refptr a = new RefCountedObject(); scoped_refptr 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的引用计数将被减为0,从而被delete掉。

7 总结

行文至此,大致对WebRTC源码中引用计数系统实现以及使用方式做了简要的介绍,现在回顾下要点

  • rtc_base中的RefCountInterface、RefCounter、RefCountedObject三个类构建起了引用计数系统。注意文章中所画的继承关系图,对理解引用计数系统的实现至关重要。
  • 引用计数的本身是利用c++的原子操作类atomic的特性来实现的。
  • 引用计数系统的使用需要配合智能指针rtc::scoped_refptr。一般不要直接使用引用计数对象的AddRef() 和 Release(),因为这种手动操作可能会由于人为编程失误引发内存泄漏。而应该通过rtc::scoped_refptr来保存、使用"引用计数对象"。
  • rtc::scoped_refptr的代码值得反复看几遍,实现方式是非常优雅的。

本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击领取↓↓


推荐阅读
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 怀疑是每次都在新建文件,具体代码如下 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 展开全部下面的代码是创建一个立方体Thisexamplescreatesanddisplaysasimplebox.#Thefirstlineloadstheinit_disp ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
author-avatar
feloveyu
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有