前言
在前面的文章中,完成了auto_ptr、unique_ptr的介绍和仿写,本文将介绍共享型智能指针——shared_ptr。
文章目录
本文所供智能指针使用的对象
class Object
{
private:
int num;
public:
Object(int x = 0) : num(x) {
cout << "Create Object: " << this << endl;
}
~Object() {
cout << "Destroy Object: " << this << endl;
}
int& value() { return num; }
const int& value() const {
return num;
}
};
一、共享型智能指针
shared_ptr是一个引用计数的智能指针,用于共享对象的所有权,即可以让多个指针指向同一个对象,这一点和原始指针相同。
1.shared_ptr的结构
共享智能指针可以理解为:
shared_ptr指向一个引用计数的结构,这个结构里有一个引用计数(有shared_ptr的引用计数,还有一个weak_ptr的引用计数,这里只说shared_ptr),还有一个指向目标对象的指针。
示例:
int main(void)
{
std::shared_ptr<Object> sp1(new Object(10));
return 0;
}
可简单理解为:
当两个指针同时指向同一个对象时,那么指向的引用计数就会加一。
int main(void)
{
std::shared_ptr<Object> sp1(new Object(10));
std::shared_ptr<Object> sp2(sp1);
return 0;
}
运行过程中可以看出,当拿一个智能指针构造另一个智能指针,两个指针除了本身的地址不同,其余都是共同指向一套东西。且引用计数变为2,表示该资源由两个指针共享。
2. 创建shared_ptr实例
- 最安全和高效的方法是调用make_shared库函数<>/font,该函数会在堆中分配一个对象并初始化,最后返回指向此对象的share_ptr实例。
- 如果你不想使用make_ptr,也可以先明确new出一个对象,然后把其原始指针传递给share_ptr的构造函数。
int main(void)
{
std::shared_ptr<Object> sp1 = std::make_shared<Object>(10);
Object* num = new Object(10);
std::shared_ptr<Object> sp2(num);
return 0;
}
3. 检查引用计数
shared_ptr提供了两个函数来检查其共享的引用计数值,分别是unique()和use_count()。
use_count()函数,该函数返回当前指针的引用计数值。值得注意的是use_count()函数可能效率很低,应该只把它用于测试或调试。
unique()函数用来测试该shared_ptr是否是原始指针唯一拥有者,也就是use_count()的返回值为1时返回true,否则返回false。
int main(void)
{
std::shared_ptr<Object> sp1 = std::make_shared<Object>(10);
Object* num = new Object(10);
std::shared_ptr<Object> sp2(num);
cout << sp1.use_count() << endl;
cout << sp2.unique() << endl;
return 0;
}
二、仿写shared_ptr
(一)辅助类:删除器
和my_unque_ptr的删除器相同,可以直接使用
1.删除单个对象
template<class _Ty>
class MyDeletor
{
public:
MyDeletor() = default;
void operator()(_Ty* ptr) const
{
if (ptr != nullptr)
{
delete ptr;
}
}
};
2.删除一组对象
template<class _Ty>
class MyDeletor<_Ty[]>
{
public:
MyDeletor() = default;
void operator()(_Ty* ptr) const
{
if (ptr != nullptr)
{
delete[]ptr;
}
}
};
(二)引用计数类
template<typename _Ty>
class RefCnt
{
private:
_Ty* mptr; // 指向对象的指针
int ref; // 引用计数
public:
RefCnt(_Ty* p = nullptr) : mptr(p), ref(mptr != nullptr)
{
}
~RefCnt() {}
};
(三) 指向单独对象的my_shared_ptr
这个类的成员变量由一个指向引用计数的指针和一个删除器组成。
在构造函数中,判断是否初始化,如果初始化为nullptr,那就什么都不做,如果不为nullptr,就在堆区申请一个引用计数类型。
template<class _Ty, class _Dx = MyDeletor<_Ty> >
class my_shared_ptr
{
public:
my_shared_ptr(_Ty* p = nullptr) :ptr(nullptr)
{
if (p != nullptr)
{
ptr = new RefCnt(p);
}
}
private:
RefCnt<_Ty>* ptr;
_Dx mDeletor;
};
1.拷贝构造函数
由于是共享型的指针,所以拷贝构造时直接将智能指针的指针成员赋值给当前this的指针,在判断是否为nullptr,若不为空,则引用计数加一。
my_shared_ptr(const my_shared_ptr& _Y) :ptr(_Y.ptr)
{
if (ptr != nullptr)
{
ptr->ref += 1;
}
}
2. 移动构造函数
移动构造函数是资源转移,所以无需其他操作,让当前this的指针成员也指向参数指针所指的引用计数,让参数的指针置为nullptr。
my_shared_ptr(my_shared_ptr&& _Y) :ptr(_Y.ptr)
{
_Y.ptr = nullptr;
}
3.重载bool()运算符
operator bool() const { return ptr != nullptr; }
4. 赋值构造函数
my_shared_ptr& operator=(const my_shared_ptr& _Y)
{
if (this == &_Y || this->ptr == _Y.ptr) return *this;
if (ptr != NULL && --ptr->ref == 0)
{
mDeletor(ptr);
}
ptr = _Y.ptr;
if (ptr != nullptr)
{
ptr->ref += 1;
}
return *this;
}
为什么赋值构造要这样写?因为要满足多种情况。
- 当自己给自己赋值时,直接返回。
- 在赋值前要先将自身的资源移交出去,即就是将自身指向的引用计数减一,如果减一后引用计数为0,那么就调用删除器将资源删除,表示没有指针再指向资源对象。之后再将参数的指针赋值给当前指针;如果当前指针不为nullptr,引用计数加一。
- 如果要拿一个空指针赋给有资源的指针:自身引用计数减一,若减完为0,则调用删除器。拿空指针赋值,然后返回。
满足多种不同情况,才算完整的写完赋值构造函数。
5. 移动赋值函数
// move operator =
my_shared_ptr& operator=(my_shared_ptr&& _Y)
{
if (this == &_Y) return *this;
if (this->ptr == _Y.ptr && this->ptr != nullptr && _Y.ptr != nullptr)
{
this->ptr->ref -= 1;
_Y.ptr = nullptr;
return *this;
}
if (this->ptr != nullptr && --ptr->ref == 0)
{
mDeletor(ptr);
}
ptr = _Y.ptr;
_Y.ptr = nullptr;
return *this;
}
同样是要考虑多种情况:
- 自赋值情况
- 两个指针指向相同的情况,如果指向相同,那么将引用计数减一,再将参数的指针置为空,即可返回。
- 如果当前指针不为空且引用计数减一后为0,那么调用删除器将资源对象删除,再用参数对其赋值。
- 若当前指针指向空,则直接将当前指针指向参数的指针所指的对象,并将参数的指针置为空。(不管参数的指针是否指向空,都可执行)。
6.reset()函数
将指针重新指向其他资源
- 若当前指针不为空,且引用计数减一后等于0,那么调用删除器;如果引用计数减一不为0,说明还有其他指针拥有该对象。
- 指针指向 重新向堆区申请引用计数的结构,该结构的指针指向参数类型的对象。
void reset(_Ty* p = nullptr)
{
if (this->ptr != nullptr && --this->ptr->ref == 0)
{
mDeletor(ptr);
}
ptr = new RefCnt<_Ty>(p);
}
7.析构函数
析构的是当前指针,那么
- 先判断是否为空,再引用计数减一,如果结果为0,则调用删除器先删除引用计数所指的对象,再删除当前指针
- 将当前指针置为空。
~my_shared_ptr()
{
if (this->ptr != nullptr && --this->ptr->ref == 0)
{
mDeletor(ptr->mptr);
delete ptr;
}
ptr = nullptr;
}
8. 观察器:重载解引用和指向符
_Ty* get() const { return ptr->mptr; }
_Ty& operator*() const
{
return *get();
}
_Ty* operator->() const
{
return get();
}
9. 获得当前引用计数个数
即查看当前对象有多少个智能指针共享
size_t use_count() const
{
if (this->ptr == nullptr) return 0;
return this->ptr->ref;
}
10.交换资源函数
void swap(my_shared_ptr& r)
{
std::swap(this->ptr, r.ptr);
}
(四)指向一组对象的my_shared_ptr
这种指针的删除器是删除一组对象的,模板也需要特化一下。
template<class _Ty, class _Dx >
class my_shared_ptr<_Ty[], _Dx>
{
private:
_Ty* ptr;
_Dx mDeletor;
public:
my_shared_ptr(_Ty* p = nullptr) :ptr(nullptr)
{
if (p != nullptr)
{
ptr = new RefCnt(p);
}
}
};
区别点
删除一组对象时大部分函数的调用删除器操作都需要改变:
需要先将指针所指向的引用计数指针传递给删除器,再删除当前指针。
示例:相关操作都需要这样改写。
if (ptr != nullptr && --ptr->ref == 0)
{
mDeletor(ptr->mptr);
delete ptr;
}
提供到被管理数组的有索引访问
_Ty& operator[](const int idx) const
{
return ptr->mptr[idx];
}