C++——智能指针 shared_ptr
C++11 中开始提供更靠谱的并且支持拷贝的 shared_ptr
RAII + 具有指针类似的行为 + 引用计数
目录
版本一:使用一个int类型内置成员变量,再进行拷贝构造的时候进行一次++ 之后赋值给新对象。
版本二:考虑到使用static静态成员变量可以为对象之间所共享
版本三:不使用静态成员变量,借助指针类型变量
版本四:为了线程安全,引入锁机制,在所有计数操作时加锁,保证操作原子性,加入DFDef保证析构的重载
四、涉及自定义类型智能指针变量线程安全实例
一、shared_ptr介绍
shared_ptr 的原理:是通过 引用计数 的方式来实现多个 shared_ptr 对象之间共享资源 。
1. shared_ptr 在其内部, 给每个资源都维护了着一份计数,用来记录该份资源被几个对象共
享 。
2. 在 对象被销毁时 ( 也就是析构函数调用 ) ,就说明自己不使用该资源了,对象的引用计数减
一。
3. 如果引用计数是 0 ,就说明自己是最后一个使用该资源的对象, 必须释放该资源 ;
4. 如果不是 0 ,就说明除了自己还有其他对象在使用该份资源, 不能释放该资源 ,否则其他对
象就成野指针了。
二、shared_ptr的使用
#include<iostream>
using namespace std;void TestSharedPtr()
{shared_ptr<int> up1(new int(10));shared_ptr<int> up2(up1);shared_ptr<int> up3;up3 = up1;
}int main()
{TestSharedPtr();_CrtDumpMemoryLeaks();return 0;
}
三、shared_ptr的模拟实现
资源可以共享:浅拷贝的基础上,可以保证资源无论被多少对象共享,最终只会释放一次。
版本一:使用一个int类型内置成员变量,再进行拷贝构造的时候进行一次++ 之后赋值给新对象。
问题: 计数有问题,不同对象之间无法实现同步共享一个计数变量!
下图就是在进行up2析构之后发现up1和up2的计数变量_count是不同值。
namespace wei
{template<class T>class shared_ptr{shared_ptr(T* ptr = nullptr): _ptr(ptr), _count(0){if (_ptr){_count = 1;}}~shared_ptr(){if (_ptr && 0 == --_count){delete _ptr;_ptr = nullptr;}}shared_ptr(shared_ptr<T>& sp): _ptr(sp._ptr), _count(++sp._count){}private:T* _ptr;int _count;};
}
版本二:考虑到使用static静态成员变量可以为对象之间所共享
析构 up2之前:
析构up2之后:
问题:当另外新的对象来的时候就会产生问题,这里的up3将所有对象所共享的_count重新经历了一次初始化,也因为所有对象共享一个_count导致了问题。
我们希望每一类对象都有属于他们的_count,up1和拷贝构造出来的up2可以使用一个_count,而新声明出来的up3对象又去使用他的_count,我们不希望他们之间进行冲突。
namespace wei
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr): _ptr(ptr){if (_ptr){_count = 1;}}~shared_ptr(){if (_ptr && 0 == --_count){delete _ptr;_ptr = nullptr;}}shared_ptr(shared_ptr<T>& sp): _ptr(sp._ptr){_count++;}private:T* _ptr;static int _count; // 静态成员变量};template<class T>int shared_ptr<T>::_count = 0;// 类内声明,类外定义
}
版本三:不使用静态成员变量,借助指针类型变量
参考我们不同对象在创建时都有不同的地址空间,例如up1和up2中的_ptr就使用同一份地址,而up3中的_ptr又有它自己新的地址,能不能利用类似于_ptr的一个东西来设计一个_count存储每一堆对象们的计数。
在成员变量中新加一个指针来进行计数。
private:T* _ptr;int* _pcount;
效果如下图:
namespace wei
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr): _ptr(ptr), _pcount(nullptr){if (_ptr){_pcount = new int(1);}}~shared_ptr(){if (_ptr && 0 == --(*_pcount)){delete _ptr;delete _pcount;_ptr = nullptr;_pcount = nullptr;}}shared_ptr(shared_ptr<T>& sp): _ptr(sp._ptr), _pcount(sp._pcount){(*_pcount)++;}private:T* _ptr;int* _pcount;};
版本四:为了线程安全,引入锁机制,在所有计数操作时加锁,保证操作原子性,加入DFDef保证析构的重载
#include <mutex>namespace bite
{template<class T>class DFDef{public:void operator()(T*& ptr){if (ptr){delete ptr;ptr = nullptr;}}};template<class T, class DF = DFDef<T>>class shared_ptr{public:// RAIIshared_ptr(T* ptr = nullptr): _ptr(ptr), _pcount(nullptr), _pMutex(nullptr){if (_ptr){_pcount = new int(1);_pMutex = new mutex();}}~shared_ptr(){Release();}// 具有指针类似行为T& operator*(){return *_ptr;}T* operator->(){return _ptr;}// 解决浅拷贝:引用计数shared_ptr(const shared_ptr<T>& sp): _ptr(sp._ptr), _pcount(sp._pcount), _pMutex(sp._pMutex){AddRef();}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (this != &sp){// *this要和sp去共享资源// 1. 先让*this和离开之前的资源Release();// 2. *this 和 sp共享资源和计数_ptr = sp._ptr;_pcount = sp._pcount;_pMutex = sp._pMutex;AddRef();}return *this;}T* get(){return _ptr;}private:void AddRef(){_pMutex->lock();++(*_pcount);_pMutex->unlock();}void Release(){bool flag = false;_pMutex->lock();if (_ptr && 0 == --(*_pcount)){DF()(_ptr);delete _pcount;_pcount = nullptr;flag = true;}_pMutex->unlock();if (flag){delete _pMutex;_pMutex = nullptr;}}private:T* _ptr;int* _pcount;mutex* _pMutex;};
}
四、涉及自定义类型智能指针变量线程安全实例
#include <thread>
#include <mutex>struct Date
{int _year = 0;int _month = 0;int _day = 0;
};void SharePtrFunc(std::shared_ptr<Date>& sp, size_t n, mutex& mtx)
{cout << sp.get() << endl;for (size_t i = 0; i < n; ++i){std::shared_ptr<Date> copy(sp);// unique_lock<mutex> lk(mtx); 保证该区域元素修改原子性copy->_year++;copy->_month++; copy->_day++;}
}void TestSharedPtr3()
{std::shared_ptr<Date> sp(new Date);cout << sp.get() << endl;size_t n = 10000;mutex m;thread t1(SharePtrFunc, std::ref(sp), n, std::ref(m)); ref的用法保证传递引用thread t2(SharePtrFunc, std::ref(sp), n, std::ref(m));t1.join(); 进行线程等待t2.join();cout << sp->_year << endl;cout << sp->_month << endl;cout << sp->_day << endl;
}int main()
{TestSharedPtr3();_CrtDumpMemoryLeaks();return 0;
}