实现简单的shared_ptr
原创
©著作权归作者所有:来自51CTO博客作者BugMaker999的原创作品,请联系作者获取转载授权,否则将追究法律责任
文章目录
类图如下:
shared_ptr和weak_ptr都有一个基类Ptr_base,Ptr_base有指向资源的指针_Ptr和指向引用计数的指针_Rep
引用计数对象Ref_count封装了对资源以及引用计数对象的释放操作,Ref_count也有一个基类Ref_count_base,封装了对引用计数_Uses、_Weaks,以及对_Uses、_Weaks的++、--操作
My_Ref_count_base
My_Ref_count_base类是一个抽象类:
- 有两个计数器变量:_Uses供shared_ptr计数用,_Weaks供weak_ptr指针计数用
- 保证线程安全:_Uses和_Weaks加1减1使用原子操作
- 如果_Uses值为0,删除资源对象,调用_Destroy()
- 如果_Weaks的值为0,删除引用计数器对象,也就是删除自己,调用Delete_this()
- 两个纯虚函数:_Destroy()删除资源对象;Delete_this()删除引用计数器对象本身
class My_Ref_count_base {
public:
// 派生类无法进行拷贝构造和赋值
My_Ref_count_base(const My_Ref_count_base&) = delete;
My_Ref_count_base& operator=(const My_Ref_count_base&) = delete;
// 虚析构,多态释放
virtual ~My_Ref_count_base(){};
void _Incref() { _Uses += 1; }
void _Incwref() { _Weaks += 1; }
void _Decref() {
if (--_Uses == 0) {
// _Uses == 0,删除资源,并将_Weaks--
_Destroy();
_Decwref();
}
}
void _Decwref() {
if (--_Weaks == 0) {
_Delete_this();
}
}
long _use_count() const {
return _Uses;
}
private:
virtual void _Destroy() = 0; // 删除资源对象
virtual void _Delete_this() = 0; // 删除引用计数器对象本身
std::atomic<int> _Uses{ 1 };
std::atomic<int> _Weaks{ 1 };
protected:
My_Ref_count_base() = default;
};
My_Ref_count
My_Ref_count的第一个派生类是My_Ref_count,无删除器的引用计数类型,它是能够实例化对象的引用计数类型,使用的时候都是用的My_Ref_count,只有把指针类型生命成My_Ref_count_base
- 数据成员_Ptr,指向资源对象地址
- 实现了My_Ref_count_base抽象基类中的纯虚函数
template<class T>
class My_Ref_count : public My_Ref_count_base{
public:
My_Ref_count(T* Px)
: My_Ref_count_base()
, _Ptr(Px)
{}
private:
// 删除资源对象
virtual void _Destroy() {
delete _Ptr;
}
// 删除引用计数器对象本身
virtual void _Delete_this() {
delete this;
}
private:
T* _Ptr;
};
My_Ptr_base
该类是My_shared_ptr和My_weak_ptr的共同基类
- 有两个指针成员,分别是element_type* _Ptr和My_Ref_count_base * _Rep
- _Ptr指向资源地址,_Rep指向引用计数器对象
template<class T>
class My_Ptr_base {
public:
using element_type = T;
My_Ptr_base(const My_Ptr_base&) = delete;
My_Ptr_base& operator=(const My_Ptr_base&) = delete;
element_type* get() const {
return _Ptr;
}
long use_count() const {
return _Rep == nullptr ? 0 : _Rep->_use_count();
}
protected:
My_Ptr_base() = default;
~My_Ptr_base() = default;
// const My_Ptr_base* this
void Incref() const {
if (nullptr != _Rep) {
_Rep->_Incref();
}
}
void Inwcref() const {
if (nullptr != _Rep) {
_Rep->_Incwref();
}
}
void Decref() const {
if (nullptr != _Rep) {
_Rep->_Decref();
}
}
void Decwref() const {
if (nullptr != _Rep) {
_Rep->_Decwref();
}
}
void Swap(My_Ptr_base& right) {
std::swap(_Ptr, right._Ptr);
std::swap(_Rep, right._Rep);
}
void _Copy_construct_from(const My_shared_ptr<T>& left) {
if (this == &left) {
return;
}
this->_Ptr = left._Ptr;
this->_Rep = left._Rep;
this->Incref();
}
// 移动构造的参数不要用const,否则不能修改参数的成员
void _Move_construct_from(My_shared_ptr<T>&& right) {
if (this == &right) {
return;
}
this->_Ptr = right._Ptr;
this->_Rep = right._Rep;
right._Ptr = nullptr;
right._Rep = nullptr;
}
protected:
element_type* _Ptr{nullptr};
My_Ref_count_base* _Rep{ nullptr };
};
My_shared_ptr
template<class T>
class My_shared_ptr : public My_Ptr_base<T> {
public:
explicit My_shared_ptr() = default;
// explicit My_shared_ptr(nullptr_t) = default;
explicit My_shared_ptr(T * Px) {
this->_Ptr = Px; // 资源
this->_Rep = new My_Ref_count<T>(Px); // 引用计数器
}
// My_shared_ptr的拷贝构造,就是需要将资源指针_Ptr和引用计数_Rep指针赋值,然后引用计数++
My_shared_ptr(const My_shared_ptr<T>& left) {
this->_Copy_construct_from(left);
}
My_shared_ptr(My_shared_ptr<T>&& right) {
this->_Move_construct_from(std::move(right)); // right为左值引用变量,本身是一个左值,需要再转换一次
}
~My_shared_ptr() {
this->Decref();
}
void swap(My_shared_ptr& right) {
this->Swap(right);
}
// 将当前对象置空
void reset() {
My_shared_ptr().swap(*this); // swap后,this有了临时对象的资源,即被置空了,临时对象有了this的资源,出作用域被析构
}
void reset(T* Px) {
My_shared_ptr(Px).swap(*this);
}
T& operator*() const { return *get(); }
T* operator->() const { return get(); }
explicit operator bool() const {
return get() != nullptr;
}
private:
using My_Ptr_base<T>::get;
};
完整代码:
class My_Ref_count_base;
template<class T>
class My_Ref_count;
template<class Resource, class Dx>
class My_Ref_count_resource;
template<class T>
class My_Ref_base;
template<class T>
class My_shared_ptr;
template<class T>
class My_weak_ptr;
// 引用计数对象的基类
class My_Ref_count_base {
public:
// 派生类无法进行拷贝构造和赋值
My_Ref_count_base(const My_Ref_count_base&) = delete;
My_Ref_count_base& operator=(const My_Ref_count_base&) = delete;
virtual ~My_Ref_count_base() {}; // 虚析构,多态释放
void _Incref() { _Uses += 1; }
void _Incwref() { _Weaks += 1; }
void _Decwref() {
if (--_Weaks == 0) {
_Delete_this();
}
}
void _Decref() {
if (--_Uses == 0) {
// _Uses == 0,删除资源,并将_Weaks--
_Destroy();
_Decwref();
}
}
long _use_count() const {
return _Uses;
}
private:
virtual void _Destroy() = 0; // 删除资源对象
virtual void _Delete_this() = 0; // 删除引用计数器对象本身
std::atomic_int _Uses{ 1 };
std::atomic_int _Weaks{ 1 };
protected:
My_Ref_count_base() = default;
};
// 无删除器的引用计数类型
template<class T>
class My_Ref_count : public My_Ref_count_base{
public:
My_Ref_count(T* Px)
: My_Ref_count_base()
, _Ptr(Px)
{}
private:
// 删除资源对象
virtual void _Destroy() {
delete _Ptr; // 删除单个对象,而不是一组对象
}
// 删除引用计数器对象本身
virtual void _Delete_this() {
delete this;
}
private:
T* _Ptr;
};
template<class T>
class My_Ptr_base {
public:
using element_type = T;
My_Ptr_base(const My_Ptr_base&) = delete;
My_Ptr_base& operator=(const My_Ptr_base&) = delete;
element_type* get() const {
return _Ptr;
}
long use_count() const {
return _Rep == nullptr ? 0 : _Rep->_use_count();
}
protected:
My_Ptr_base() = default;
~My_Ptr_base() = default;
// const My_Ptr_base* this
void Incref() const {
if (nullptr != _Rep) {
_Rep->_Incref();
}
}
void Inwcref() const {
if (nullptr != _Rep) {
_Rep->_Incwref();
}
}
void Decref() const {
if (nullptr != _Rep) {
_Rep->_Decref();
}
}
void Decwref() const {
if (nullptr != _Rep) {
_Rep->_Decwref();
}
}
void Ptr_base_Swap(My_Ptr_base& right) {
std::swap(_Ptr, right._Ptr);
std::swap(_Rep, right._Rep);
}
protected:
void _Copy_construct_from(const My_shared_ptr<T>& left) {
if (this == &left) {
return;
}
this->_Ptr = left._Ptr;
this->_Rep = left._Rep;
this->Incref();
}
// 因为weak_ptr可以通过weak_ptr和shared_ptr进行拷贝构造,所有这里形参用My_Ptr_base,可以接收weak_ptr和shared_ptr实参
void _Weak_Copy_construct_from(const My_Ptr_base<T>& left) {
if (this == &left) {
return;
}
// weak_ptr只关心引用计数器是否存活
if (nullptr != left._Rep) {
this->_Ptr = left._Ptr;
this->_Rep = left._Rep;
this->Inwcref();
}
}
// 移动构造的参数不要用const,否则不能修改参数的成员
void _Move_construct_from(My_Ptr_base<T>&& right) {
if (this == &right) {
return;
}
this->_Ptr = right._Ptr;
this->_Rep = right._Rep;
right._Ptr = nullptr;
right._Rep = nullptr;
}
protected:
element_type* _Ptr{nullptr};
My_Ref_count_base* _Rep{ nullptr };
};
template<class T>
class My_shared_ptr : public My_Ptr_base<T> {
public:
explicit My_shared_ptr() = default;
// explicit My_shared_ptr(nullptr_t) = default;
explicit My_shared_ptr(T * Px) {
this->_Ptr = Px; // 资源
this->_Rep = new My_Ref_count<T>(Px); // 引用计数器
}
// My_shared_ptr的拷贝构造,就是需要将资源指针_Ptr和引用计数_Rep指针赋值,然后引用计数++
My_shared_ptr(const My_shared_ptr<T>& left) {
this->_Copy_construct_from(left);
}
My_shared_ptr(My_shared_ptr<T>&& right) {
this->_Move_construct_from(std::move(right)); // right为左值引用变量,本身是一个左值,需要再转换一次
}
My_shared_ptr(const My_weak_ptr<T>& left) {
this->_Copy_construct_from(left);
}
~My_shared_ptr() {
this->Decref();
}
void swap(My_shared_ptr& right) {
this->Swap(right);
}
// 将当前对象置空
void reset() {
My_shared_ptr().swap(*this); // swap后,this有了临时对象的资源,即被置空了,临时对象有了this的资源,出作用域被析构
}
void reset(T* Px) {
My_shared_ptr(Px).swap(*this);
}
T& operator*() const { return *get(); }
T* operator->() const { return get(); }
explicit operator bool() const {
return get() != nullptr;
}
private:
using My_Ptr_base<T>::get;
};
直接使用构造函数和使用make_shared的区别
手动调用new缺陷: 如果 shared_ptr sp(new int(10)) 这行代码中的new int(10) 执行成功了,而shared_ptr的构造函数执行失败了,就不会有引用计数的_Ref_count对象,就意味着不存在shared_ptr对象,就不会调用shared_ptr的析构函数,那我们new出来的堆区资源也就不会释放了
手动调用new好处: 如果我们手动调用构造函数生成shared_ptr对象,资源和引用计数对象是分开存放在堆区的,当Uses_为0,Weaks_不为0时,会把资源释放,而不释放引用计数对象,即相对于使用make_shared资源对象可以提前释放。只有Uses_和Weaks_同时为0,引用计数对象才会被释放。
make_shared优点:
- make_shared把资源和引用计数的对象放在连续的空间中,只需要new一次,解决了上面的问题。new失败没有资源泄露,new成功析构函数会释放资源
- 根据程序运行的局部性原理,将资源对象和引用计数器对象放在一起比分开放,能减少一半的cache miss
make_shared缺点:
- 由于使用make_shared分配的资源空间和引用计数对象的空间是连续的,是一次性申请的,也需要一次性释放。所以对于make_shared new出来的内存,就算引用计数_Uses为0,而_Weaks不为0,无论是int(10)的空间还是引用计数对象空间都不能释放,因为_Weaks不为0,引用计数对象空间不能释放,整块资源都不能释放
- 当资源对象很大时,Uses_为0,也不能及时释放,会占用系统空间。而使用make_shared可以减少内存分配的次数,且能防止了资源泄露
- 无法自定义删除器,默认析构函数是delete,无法管理打开的文件。如果想自定义删除器,还得手动调用构造函数