我们知道auto_ptr,unique_ptr构造或者析构后,都会将自身的管理权限转移,那么如何解决这个问题呢,boost库也给我们提供了带引用计数的指针。
1:shared_ptr
shared_ptr是最常用的智能指针,shared_ptr采用了引用计数器,多个shared_ptr中的T *ptr指向同一个内存区域(同一个对象),并共同维护同一个引用计数器。shared_ptr定义如下,记录同一个实例被引用的次数,当引用次数大于0时可用,等于0时释放内存。所以用户可以自由的赋值和构造,只要其引用计数不为0,对象所指的内存就不会被析构掉,也就不会产生错误。
在shared_ptr的操作中,我们对于动态内存的开辟及初始化最好是使用make_shared这个函数模板,而不是使用new,为什么呢?
同直接使用new相比,make函数减小了代码重复,提高了异常安全,并且对于std::make_shared和std::allcoated_shared,生成的代码会更小更快。另外使用 std::shared_ptr 如果用 new 来调用,这使得代码出现了某种程度上的不对称。于是在编码中尽量不使用new,使用make_shared,函数模板。std::make_shared 就能够用来消除显示的使用 new,所以std::make_shared 会分配创建传入参数中的对象,并返回这个对象类型的std::shared_ptr指针。
具体的new与make_shared的比较大家可以参考这个链接https://blog.csdn.net/coolmeme/article/details/43405155
可以看出,我们并不能隐式的用new来构造对象,但是我们可以用make_shared这个函数模板隐式构造。
shared_ptr可以通过get()函数获取到原始指针(获取的时候并不会增加引用计数),还可以通过reset()函数减少一个引用计数,相当于将一个智能指针对象指向NULL;看以通过use_count()函数获取引用计数。
#include<iostream>
using namespace std;
int main()
{
shared_ptr<int> s1 = make_shared<int>(20);
shared_ptr<int> s2 = s1; // 引用计数+1
shared_ptr<int> s3 = s1; // 引用计数+1
int *p = s1.get(); //只能用它的原始类型来接收,当然void *也可以,这样不会增加引用计数
cout<<"s1.use_count() = "<<s1.use_count()<<endl; // 3
cout<<"s2.use_count() = "<<s2.use_count()<<endl; // 3
cout<<"s3.use_count() = "<<s3.use_count()<<endl; // 3
s2.reset(); //将
cout<<"reset s2:"<<endl;
cout<<"s1.use_count() = "<<s1.use_count()<<endl; // 2
cout<<"s2.use_count() = "<<s2.use_count()<<endl; // 0,s2已经reset
cout<<"s3.use_count() = "<<s3.use_count()<<endl; // 2
}
我们来实现一个简单的shared_ptr(实现方法很多,我只列举一二)
<一>指针与引用计数绑定,用map表实现
//shared_ptr 引用计数用map表来表示的第一种实现方式
template<typename T>
class SharedPtr
{
public:
SharedPtr(T *p=NULL):ptr(p)
{
mmap.insert(make_pair(ptr,1)); //将ptr及其引用计数插入map表中
}
SharedPtr(const SharedPtr & rt) //拷贝构造函数
{
ptr=rt.ptr;
mmap[ptr]++; //通过mmap[]里的键就可以访问到键所对应的值,将值增加
}
SharedPtr& operator=(const SharedPtr & rt)
{
if(&rt!=this) //判断自赋值
{
if(--mmap[ptr]<=0) //如果被赋值的对象只有一个指向那块内存,就要讲内存释放,并且在map表中将数据擦去
{
if(ptr!=NULL)
{
delete ptr; //释放内存
ptr=NULL;
}
mmap.erase(ptr); //擦去map表中的数据
}
ptr=rt.ptr;
mmap[ptr]++;
return *this;
}
}
T &operator *()
{
return *ptr;
}
T *operator->()
{
return ptr;
}
~SharedPtr()
{
if(--mmap[ptr]<=0) //判断是否此对象是内存指向的最后一个对象
{
if(ptr!=NULL)
{
delete ptr; //释放掉内存
ptr=NULL;
}
mmap.erase(ptr); //在map表中擦掉数据
}
}
private:
static map<T *,int>mmap; //同一个类共用一个map表
T *ptr;
};
template<typename T>
map<T *,int> SharedPtr<T>::mmap; //map表有一个特性,不能插入键相同的数据,虽然插入不会出错,但是不会改变原先的键所对应的值,后插的是插不进去的
未完。。。。