一、前言:
C++11之前我们程序员都是按照new/delete这么去管理堆对象生命周期的,可是如果new了忘记delete就会造成内存泄漏,我们不能光用勤奋去保证质量,人和动物的本质区别就是使用工具,于是乎智能指针就问世了。可是WebRtc当中不管使用了C++本身的智能指针,还有自己一套办法,而且随处可见,值得我们研究下。
二、RefCountedObject:
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
类对象 obj1
和 obj2
,然后分别使用 scoped_refptr
类 ptr1
和 ptr2
来管理这两个对象的指针。
通过调用 ptr1.swap(&ptr2);
,我们交换了 ptr1
和 ptr2
所指向的对象。在交换后,ptr1
指向了原来 ptr2
所管理的对象,而 ptr2
则指向了原来 ptr1
所管理的对象。
四、总结:
rtc_base中的RefCountInterface、RefCounter、RefCountedObject都是有自身引用计数的,我们解释了RefCountedObject和scoped_refptr如何进行配合,也就理解了webrtc里面如何进行智能管理对象生命周期。
欢迎关注vx公众号: