shared_ptr的概念
shared_ptr实现共享式拥有(shared ownership)概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用(reference)被销毁”时候释放。
基本原理
智能指针是(几乎总是)模板类,shared_ptr 同样是模板类,所以在创建 shared_ptr 时需要指定其指向的类型。shared_ptr 负责在不使用实例时释放由它管理的对象,同时它可以自由的共享它指向的对象。 shared_ptr 使用经典的 “引用计数 ” 的方法来管理对象资源。
模拟实现理解原理
在系统提供的共享性智能指针中,存在一个观察器use_count 用来返回shared_ptr 所管理对象的引用计数,使用样例如下: 
那么,其具体实现是怎样的呢?我们来模仿源码实现一下:
template<class _Ty>
class RefCnt
{
public:
_Ty* mptr;
std::atomic_int ref;
public:
RefCnt(_Ty* p = nullptr) :mptr(p), ref(mptr != nullptr) {
~RefCnt() {}
};
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);
}
}
my_shared_ptr(const my_shared_ptr& _Y) :ptr(_Y.ptr)
{
if (ptr != nullptr)
{
ptr->ref += 1;
}
}
my_shared_ptr(my_shared_ptr&& _Y) :ptr(_Y.ptr)
{
_Y.ptr = nullptr;
}
operator bool() const { return ptr != nullptr; }
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;
}
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;
}
void reset(_Ty* p = nullptr)
{
if (this->ptr != nullptr && --this->ptr->ref == 0)
{
mDeletor(ptr);
}
ptr = new RefCnt<_Ty>(p);
}
~my_shared_ptr()
{
if (this->ptr != nullptr && --this->ptr->ref == 0)
{
mDeletor(ptr->mptr);
delete ptr;
}
ptr = nullptr;
}
_Ty* get() const { return ptr->mptr; }
_Ty& operator*() const
{
return *get();
}
_Ty* operator->() const
{
return get();
}
size_t use_count() const
{
if (this->ptr == nullptr) return 0;
return this->ptr->ref;
}
void swap(my_shared_ptr& r)
{
std::swap(this->ptr, r.ptr);
}
private:
RefCnt<_Ty>* ptr;
_Dx mDeletor;
};
template<class _Ty, class _Dx >
class my_shared_ptr<_Ty[],_Dx>
{
public:
my_shared_ptr(_Ty* p = nullptr) :ptr(nullptr)
{
if (p != nullptr)
{
ptr = new RefCnt(p);
}
}
my_shared_ptr(const my_shared_ptr& _Y) :ptr(_Y.ptr)
{
if (ptr != nullptr)
{
ptr->ref += 1;
}
}
my_shared_ptr(my_shared_ptr&& _Y) :ptr(_Y.ptr)
{
_Y.ptr = nullptr;
}
operator bool() const { return ptr != nullptr; }
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->mptr);
delete ptr;
}
ptr = _Y.ptr;
if (ptr != nullptr)
{
ptr->ref += 1;
}
return *this;
}
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->mptr);
delete ptr;
}
ptr = _Y.ptr;
_Y.ptr = nullptr;
return *this;
}
void reset(_Ty* p = nullptr)
{
if (this->ptr != nullptr && --this->ptr->ref == 0)
{
mDeletor(ptr->mptr);
delete ptr;
}
ptr = new RefCnt<_Ty>(p);
}
~my_shared_ptr()
{
if (this->ptr != nullptr && --this->ptr->ref == 0)
{
mDeletor(ptr->mptr);
delete ptr;
}
ptr = nullptr;
}
_Ty* get() const { return ptr->mptr; }
_Ty& operator*() const
{
return *get();
}
_Ty* operator->() const
{
return get();
}
size_t use_count() const
{
if (this->ptr == nullptr) return 0;
return this->ptr->ref;
}
void swap(my_shared_ptr& r)
{
std::swap(this->ptr, r.ptr);
}
_Ty& operator[](const int idx) const
{
return ptr->mptr[idx];
}
private:
RefCnt<_Ty>* ptr;
_Dx mDeletor;
};
根据代码:我们分析到对象的内存结构图如下:这有助于我们更好地理解: 
相互引用问题
 退出之前,它们的 use_count() 都为2,退出了 fun() 后,由于 c和 p 对象互相引用,它们的引用计数都是 1,不能自动释放(可以看到没有调用析构函数),并且此时这两个对象再无法访问到。这就引起了c++中那臭名昭著的“内存泄漏”。
我们解决这个问题就可以使用weak_ptr : 使用weak_ptr 来打破循环引用,它与一个 shared_ptr 绑定,但却不参与引用计数的计算,不论是否有 weak_ptr 指向,一旦最后一个指向对象的 shared_ptr 被销毁,对象就会被释放。weak_ptr 像是 shared_ptr 的一个助手。同时,在需要时,它还能摇身一变,生成一个与它绑定的 shared_ptr 共享引用计数的shared_ptr。
多线程下的shared_ptr存在的问题
因为在多线程下,多个线程很有可能对同一个shared_ptr 的引用计数进行操作,然而在我们设计的shared_ptr 中的引用计数为int类型,对于int的一个操作其实并不是原子操作,那么多线程环境下就不能保证安全的运行。 于是我们会对引用计数进行一个重新的设计: 引入#include<atomic> ,修改引用计数的类型如下:  从而解决这一问题。
总结
(1) 智能指针主要的用途就是方便资源的管理,自动释放没有指针引用的资源。
(2) 使用引用计数来标识是否有多余指针指向该资源。(注意,shart_ptr本身指针会占1个引用)
(3) 在赋值操作中, 原来资源的引用计数会减一,新指向的资源引用计数会加一。
(4) 引用计数加一/减一操作是原子操作,所以线程安全的。
(5) make_shared要优于使用new,make_shared可以一次将需要内存分配好。(第一张图片中有解释)
(6) std::shared_ptr的大小是原始指针的两倍,因为它的内部有一个原始指针指向资源,同时有个指针指向引用计数。
(7) 引用计数是分配在动态分配的,std::shared_ptr支持拷贝,新的指针获可以获取前引用计数个数。
|