文章目录

类图如下:

实现简单的shared_ptr_构造函数

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的区别

实现简单的shared_ptr_开发语言_02


手动调用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,引用计数对象才会被释放。

实现简单的shared_ptr_拷贝构造_03


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,无法管理打开的文件。如果想自定义删除器,还得手动调用构造函数