版權聲明:原創文章,歡迎轉載,但請註明出處,謝謝。https://blog.csdn.net/qiuguolu1108/article/details/104324440
更多關於WebRTC源碼剖析的文章,請點擊《WebRTC源碼剖析》。
文章目錄
unique_ptr
和scoped_refptr
是WebRTC中使用最多的兩種智能指針,unique_ptr
是C++標準庫中的智能指針,而scoped_refptr
是WebRTC中自實現的智能指針,用法類似於C++標準庫中shared_ptr
,但使用上不如shared_ptr
方便,功能也不如shared_ptr
豐富。
本文主要簡單介紹一下如何使用scoped_refptr
,同時通過源碼看一下scoped_refptr
是如何實現的。
scoped_refptr使用示例
#define WEBRTC_WIN
#include <iostream>
#include "scoped_refptr.h"
#include "ref_count.h"
#include "ref_counted_object.h"
using namespace std;
using namespace rtc;
class A: public RefCountInterface /*需要繼承自類RefCountInterface*/
{
public:
A(int i)
:data_(i)
{
cout << __FUNCTION__ << " : " << this << endl;
}
void display()
{
cout << "data = " << data_ << endl;
}
void set(int i)
{
data_ = i;
}
~A()
{
cout << __FUNCTION__ << endl;
}
private:
int data_;
};
void func(scoped_refptr<A> sp)
{
sp->display();
sp->set(200);
}
int main()
{
/*只見new,不見delete。*/
scoped_refptr<A> sp = new RefCountedObject<A>(100);
/*獲取託管對象的地址*/
cout << "sp = " << sp.get() << endl;
sp->display();
func(sp);
sp->display();
return 0;
}
使用方法:
- 類A需要繼承自
RefCountInterface
類。 - 在new A的時候,需要使用模板類
RefCountedObject
進行包裝,類A作爲RefCountedObject
的模板參數,後面括號中是構造類A對象的參數。 new RefCountedObject<A>(100)
產生的指針,交於sp
對象託管,指針的引用計數和釋放都是通過sp
對象自動管理的。這樣只需要new對象,而對象的釋放不需要再主動調用delete,`當引用計數爲零的時候,會主動釋放new出的對象。
scoped_refptr源碼剖析
託管對象使用的方法
scoped_refptr對託管對象的管理主要是通過資源獲取即初始化(RAII)和引用計數。
資源獲取即初始化(RAII)
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "constructor" << endl;
}
~A()
{
cout << "deconstructor" << endl;
}
};
class Smart_ptr
{
public:
Smart_ptr(A* pa)
:pa_(pa)
{}
~Smart_ptr()
{
delete pa_;
}
private:
A* pa_;
};
int main()
{
Smart_ptr sp = new A();
return 0;
}
傳統的智能指針auto_ptr
對託管對象的管理使用的就是資源獲取即初始化(RAII)。在C++中對內存的管理一直是一件令人頭疼的事情,很多時候new了對象以後,會忘記delete,這樣就會造成內存泄漏。而RAII利用的是類對象在離開作用域時會主動調用析構器這一特性,將delete這件事放在析構器中,這樣就不會忘記釋放內存了。
RAII的主要做法是這樣的:假如我們需要在堆上new一個類A的對象。再在棧上定義一個類Smart_ptr的對象sp,對象sp在棧上,所以在對象sp離開它的作用域時,會主動調用類Smart_ptr的析構器,對堆上內存的管理主要利用這一特性。sp對象中數據成員pa_保存new A()的地址,在Smart_ptr的構造器中傳入托管的地址,在sp離開其作用域的時候,會調用其析構器,在析構器中delete掉託管的內存,這樣就不會造成堆內存的泄漏。
在使用的時候,只看見new,是看不見delete的。使用的時候new出對象,將內存託管,至於何時delete掉對象不需要再關心。這樣大大簡化了類對象的申請和釋放。
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "constructor" << endl;
}
void display()
{
cout << "hello world" << endl;
}
~A()
{
cout << "deconstructor" << endl;
}
};
class Smart_ptr
{
public:
Smart_ptr(A* pa)
:pa_(pa)
{}
A& operator*()
{
return *pa_;
}
A* operator->()
{
return pa_;
}
~Smart_ptr()
{
delete pa_;
}
private:
A* pa_;
};
int main()
{
Smart_ptr sp = new A();
sp->display();
(*sp).display();
return 0;
}
爲了使sp指針使用起來感覺就像類A對象指針似的,需要對Smart_ptr類重載operator*()和operator->(),這樣sp指針就像A對象指針似的。
還有一個問題是Smart_ptr類對象只能託管類A的對象,不能託管其他類對象,使用太侷限。可以通過模板使Smart_prt類可以託管任意對象。如下代碼:
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A constructor" << endl;
}
void display()
{
cout << "hello world" << endl;
}
~A()
{
cout << "A deconstructor" << endl;
}
};
class B
{
public:
B()
{
cout << "B constructor" << endl;
}
void display()
{
cout << "hello world" << endl;
}
~B()
{
cout << "B deconstructor" << endl;
}
};
template<typename T>
class Smart_ptr
{
public:
Smart_ptr(T* pa)
:pa_(pa)
{}
T& operator*()
{
return *pa_;
}
T* operator->()
{
return pa_;
}
~Smart_ptr()
{
delete pa_;
}
private:
T* pa_;
};
int main()
{
Smart_ptr<A> spa = new A();
spa->display();
Smart_ptr<B> spb = new B();
spb->display();
return 0;
}
通過模板的方式,實現了Smart_ptr類對任意對象的託管。
引用計數
上面的Smart_ptr對對象的指針的託管還是有問題的,如在傳參的時候,如下代碼:
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A constructor" << endl;
}
void display()
{
cout << "hello world" << endl;
}
~A()
{
cout << "A deconstructor" << endl;
}
};
template<typename T>
class Smart_ptr
{
public:
Smart_ptr(T* pa)
:pa_(pa)
{}
T& operator*()
{
return *pa_;
}
T* operator->()
{
return pa_;
}
~Smart_ptr()
{
delete pa_;
}
private:
T* pa_;
};
void func(Smart_ptr<A> sp)
{
sp->display();
}
int main()
{
Smart_ptr<A> sp = new A();
sp->display();
func(sp);
return 0;
}
這樣會造成重析構(double free),即對同一段內存釋放了兩次。main函數中的sp和func函數中的sp同時託管同一個堆對象,在func函數調用結束後,其內部的sp離開作用域調用析構器釋放託管的內存。在main函數中sp在離開作用域時再次釋放託管的內存,同一個堆對象被釋放了兩次,造成了內存錯誤。
爲了解決這個問題,引入了引用計數,只要在引用計數爲零的時候,才釋放內存。在scoped_refptr智能指針中,通過繼承的方式給託管對象增加引用計數的功能。當有多個scoped_refptr對象託管同一個對象的時候,被託管對象的引用計數會增加,當託管對象的引用計數爲零時,調用delete將其析構掉。
類的關係圖
對託管對象的包裝
RefCountInterface
RefCountInterface類所在文件的位置:src\rtc_base\ref_count.h
enum class RefCountReleaseStatus { kDroppedLastRef, kOtherRefsRemained };
class RefCountInterface
{
public:
/*增加引用計數*/
virtual void AddRef() const = 0;
/*減少引用計數,當引用計數爲零是,釋放託管的對象。*/
virtual RefCountReleaseStatus Release() const = 0;
protected:
virtual ~RefCountInterface() {}
};
RefCountInterface是一個接口類,提供了兩個函數AddRef()和Release(),這個兩個函數分別用於增加引用計數和減少引用計數且當引用計數爲零時釋放對象。
所有需要通過智能指針scoped_refptr託管的類需要繼承自這個接口。
RefCounter
RefCounter類所在文件的位置:src\rtc_base\ref_counter.h
class RefCounter
{
public:
explicit RefCounter(int ref_count)
: ref_count_(ref_count) {}
/*不可以無參構造*/
RefCounter() = delete;
/*增加引用計數*/
void IncRef()
{
rtc::AtomicOps::Increment(&ref_count_);
}
/*減少引用計數,同時返回引用計數是否爲零。*/
rtc::RefCountReleaseStatus DecRef()
{
return (rtc::AtomicOps::Decrement(&ref_count_) == 0)
? rtc::RefCountReleaseStatus::kDroppedLastRef
: rtc::RefCountReleaseStatus::kOtherRefsRemained;
}
/*返回引用計數是否爲1*/
bool HasOneRef() const
{
return rtc::AtomicOps::AcquireLoad(&ref_count_) == 1;
}
private:
volatile int ref_count_; /*用於引用計數*/
};
RefCounter類是單獨用於引用計數的類,其內部數據成員ref_count
用於記錄指針被引用的次數。
AtomicOps
類提供了函數Increment()和Decrement(),用於原子的增加一和減少一,避免使用鎖來確保原子操作。
DecRef()
函數在將引用計數減一的同時,需要判斷引用計數是否爲零,當引用計數爲零的時候,需要釋放託管的對象。DecRef()通過返回值告知調用者引用計數是否爲零。
RefCountedObject
RefCountedObject類所在文件的位置:src\rtc_base\ref_counted_object.h
template <class T>
class RefCountedObject : public T /*繼承自模板參數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;
}
/*引用計數是否爲1*/
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);
};
在new託管對象的時候,需要使用RefCountedObject包裝。
在開始的例子中scoped_refptr<A> sp = new RefCountedObject<A>(100);,爲了託管類A的對象,需要把類A作爲模板參數生成RefCountedObject對象,RefCountedObject是一個模板類,它的參數是類A,同時RefCountedObject又繼承自它的模板參數,所以RefCountedObject是類A的子類,new RefCountedObject<A>(100)
得到的是RefCountedObject *類型的指針。RefCountedObject中有引用計數的功能,通過繼承的方式RefCountedObject既有類A的功能,又有引用計數的功能。當一個類有了引用計數的功能就可以配合scoped_refptr智能指針進行內存託管了。具體細節在scoped_refptr中詳述。
注意在引用計數減少的時候,當判斷引用計數爲零時,說明沒有智能指針再對本對象的引用了,需要將本對象釋放掉。
數據成員ref_count_
被mutable
修飾的原因是,const修飾的成員函數(例如AddRef()和Release())
中不能對數據成員修改,但被mutable
修飾過的數據成員是可以在const成員函數中修改的。至於爲什麼用const修飾AddRef()和Release()函數,是爲了const類型RefCountedObject對象可以調用這個兩個函數。
RTC_DISALLOW_COPY_AND_ASSIGN(RefCountedObject)
是通過宏的方式禁用拷貝構造和賦值運算符重載,宏原型和展開結果如下:
宏原型
#define RTC_DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&) = delete; \
RTC_DISALLOW_ASSIGN(TypeName)
#define RTC_DISALLOW_ASSIGN(TypeName) \
TypeName& operator=(const TypeName&) = delete
展開結果
RefCountedObject(const RefCountedObject&) = delete;
RefCountedObject& operator=(const RefCountedObject&) = delete
小結
爲託管類A對象,類A需要繼承自類RefCountInterface,在new A的時候,沒有直接申請類A,而是new RefCountedObject<A>
,得到的是RefCountedObject對象指針,只是把類A作爲RefCountedObject模板類的參數,RefCountedObject是類A的子類。
通過繼承和模板類繼承的方式,對託管類A進行了包裝,包裝後的類A增加了引用計數的功能。
對託管對象的管理
scoped_refptr的概述
scoped_refptr類所在文件的位置:src\api\scoped_refptr.h
scoped_refptr類的主要作用是,管理被託管對象的指針,同時在使用形式上儘量做到和被託管對象指針一致。
template <class T> /*模板類*/
class scoped_refptr
{
public:
/*其他成員函數*/
protected:
T* ptr_; /*託管對象的指針*/
};
scoped_refptr是模板類,可以進行不同的實例化,託管不同的對象。
構造器和析構器
/*無參構造,將託管的指針置爲nullptr。*/
scoped_refptr()
: ptr_(nullptr)
{}
/*有參構造 保存託管的指針,同時將對象內的引用計數增加一。*/
scoped_refptr(T* p)
: ptr_(p) // NOLINT(runtime/explicit)
{
if (ptr_)
ptr_->AddRef(); /*增加託管對象的引用計數*/
}
/*拷貝構造 共同託管一個對象*/
scoped_refptr(const scoped_refptr<T>& r)
: ptr_(r.ptr_)
{
if (ptr_)
ptr_->AddRef();
}
/*拷貝構造器 類成員函數模板*/
template <typename U>
scoped_refptr(const scoped_refptr<U>& r)
: ptr_(r.get())
{
if (ptr_)
ptr_->AddRef();
}
/*移動構造器*/
scoped_refptr(scoped_refptr<T>&& r)
: ptr_(r.release()) /*r放棄託管的指針,本類接管託管的指針。*/
{}
template <typename U>
scoped_refptr(scoped_refptr<U>&& r)
: ptr_(r.release()) {}
/*智能指針對象在析構的時候,需要將託管的指針引用計數減一。*/
~scoped_refptr()
{
if (ptr_)
ptr_->Release(); /*當引用計數爲零時,會釋放託管的對象。*/
}
再用開頭的例子分析一下託管的過程:
scoped_refptr<A> sp = new RefCountedObject<A>(100);
RefCountedObject<A>是類A的子類,new RefCountedObject<A>(100)得到的是RefCountedObject<A>對象的指針,也就是類A子對象的指針。
scoped_refptr<A>使用類A進行實例化,則scoped_refptr數據成員T * ptr_中的T *是A *,即保存的指針是類A的指針。在調用scoped_refptr構造器構造對象時,傳入的卻是RefCountedObject<A> *,即將子類對象的指針賦值給父類對象的指針,發生了賦值兼容,這是可以的。並且在scoped_refptr數據成員ptr_調用AddRef()和Release()函數時,會發生多態行爲。
template <typename U>
scoped_refptr(const scoped_refptr<U>& r)
: ptr_(r.get())
{
if (ptr_)
ptr_->AddRef();
}
單獨說一下這個構造器,這個構造器是一個類成員函數模板構造器。我現在看WebRTC的代碼還太少,還沒看到這個構造器在WebRTC中是如何使用的。根據自己的理解,找到了一個可能的用法。
根據const scoped_refptr<U>& r
知,對象r中託管的指針是類U的指針,若scoped_refptr實例化爲類A類型,則託管的是類A的指針,ptr_(r.get())
說明類U的指針可以賦值給類A的指針變量。不經過指針類型強轉可以這樣進行指針賦值的,常用的就是子類指針賦值給父類指針變量。所以一種存在的可能是類U是類A的子類。示例如下:
#define WEBRTC_WIN
#include <iostream>
#include "scoped_refptr.h"
#include "ref_count.h"
#include "ref_counted_object.h"
using namespace std;
using namespace rtc;
class A: public RefCountInterface
{
public:
void display()
{
cout << "class A" << endl;
}
virtual void dump()
{
}
};
class B :public A
{
public:
void dump() override
{
cout << "class B" << endl;
}
};
int main()
{
scoped_refptr<B> spb = new RefCountedObject<B>();
/*發生拷貝構造器,調用scoped_refptr(const scoped_refptr<U>& r)。*/
scoped_refptr<A> spab = spb;
spab->display();
spab->dump();
return 0;
}
spb對象
託管的是類B的指針,spab對象
託管的是類A的指針。在構造spab對象
的時候會調用template <typename U> scoped_refptr(const scoped_refptr<U>& r)函數。
運算符重載函數
/* 轉換構造器 用於轉成T*指針*/
operator T* () const { return ptr_; }
/* ->運算符重載*/
T* operator->() const { return ptr_; }
/*賦值運算符重載*/
scoped_refptr<T>& operator=(T* p)
{
if (p)
p->AddRef(); /*增加託管對象的引用計數*/
if (ptr_)
ptr_->Release(); /*將原託管對象的引用計數減一*/
ptr_ = p; /*託管新對象*/
return *this;
}
/*賦值運算符重載*/
scoped_refptr<T>& operator=(const scoped_refptr<T>& r)
{
return *this = r.ptr_; /*調用上面的賦值運算符重載*/
}
template <typename U>
scoped_refptr<T>& operator=(const scoped_refptr<U>& r)
{
return *this = r.get();
}
/*移動賦值運算符重載*/
scoped_refptr<T>& operator=(scoped_refptr<T>&& r)
{
scoped_refptr<T>(std::move(r)).swap(*this);
return *this;
}
template <typename U>
scoped_refptr<T>& operator=(scoped_refptr<U>&& r)
{
scoped_refptr<T>(std::move(r)).swap(*this);
return *this;
}
說一下scoped_refptr<T>& operator=(scoped_refptr<T>&& r)
函數,這個函數實現了移動賦值運算符重載。scoped_refptr<T>(std::move®)調用移動拷貝構造器產生了臨時對象,此時臨時對象託管的是r對象
託管的指針,scoped_refptr<T>(std::move®).swap(*this);臨時對象和本對象交換託管的對象,則本對象託管r對象
託管的指針,而臨時對象託管本對象託管的指針,臨時對象離開時會把本對象之前託管的指針計數減一。從而實現了將本對象託管的指針引用計數減一,並放棄對此指針的託管,改爲託管r對象
託管的指針。
其他成員函數
/*獲取託管的指針*/
T* get() const { return ptr_; }
/*放棄指針託管,並將託管對象返回。*/
T* release()
{
T* retVal = ptr_;
ptr_ = nullptr; /*放棄託管,置爲nullptr。*/
return retVal; /*返回託管的指針*/
}
/*通過傳遞二級指針,交換一級指針。*/
void swap(T** pp)
{
T* p = ptr_;
ptr_ = *pp;
*pp = p;
}
void swap(scoped_refptr<T>& r)
{
swap(&r.ptr_);
}
小結
以上就是對WebRTC中scoped_refptr
智能指針的理解,如有錯誤敬請見諒,歡迎指出錯誤,我會虛心接受並加以改正。