目录
一、智能指针的理解
二、智能指针的类型
1.引用计数
2.循环引用问题
3.weak_ptr处理逻辑
五、定制删除器
六、源码
一、智能指针的理解
问题:什么是智能指针?为什么要有智能指针?智能指针解决什么样的问题?
要明白上面这些问题,我们先引入一个程序。
double div(double x, double y)
{
int* p = new int(120);
if (y == 0)
{
string s = "y can not be zero.";
throw s;
}
delete p;
return x / y;
}
int main()
{
try
{
double x = 2, y = 0;
div(x, y);
}
catch (string s)
{
cout << s << endl;
}
return 0;
}
上面这段程序,如果出现抛异常的话,p的内存是没有办法释放的。
智能指针简称RAll,是一种自动化管理资源的类模板,这里指的资源可以是:动态开辟的内存,文件指针,网络连接,互斥锁等等。RAII在获取资源时把资源委托给⼀个对象,接着控制对资源的访问,利用对象的⽣命周期结束时会自动析构的特性来完成对资源的自动释放,这样保障了资源的正常释放,避免资源泄漏问题。
二、智能指针的类型
智能指针其实就是在原指针的基础上套一个类,如果就这样简单的想会有一个非常致命问题——当一块空间被两个智能指针对象指向时会析构两次,从而引发编译器报错。
库中的智能指针有很多,用法都一样,区别在于处理同一块空间会被释放两次的方法不同。接下来我们就来看这些智能指针是如何处理的。
- auto_ptr:在做拷贝构造和赋值重载时,把原对象的资源管理权直接转移给新的对象,原对象指向空,保证一块资源只被一个智能指针管理,从而达到一块资源只被释放一次的目的。但是这种做法会使原指针悬空,如果被误用会导致访问报错的问题。推荐指数:⭐
- unique_ptr:不允许做拷贝构造和赋值重载操作,从而达到一块资源只被释放一次的目的。如果用不到这两个函数的话可以考虑用这个智能指针。推荐指数:⭐⭐⭐⭐
- shared_ptr:支持多个对象指向同一块资源,它底层用的是引用计数的方法,这是一个很优秀的智能指针,在下文我们会重点来学习该指针的具体原理和实现。推荐指数:⭐⭐⭐⭐⭐
三、shared_ptr的原理
1.引用计数
原理:使用一个变量记录有几个对象在管理该资源,从而决定是否释放该资源。例如:当一个对象走到析构函数时也就是该对象生命结束了不再管理这块资源了,所以引用计数减一,此时如果引用计数变成0的话说明该资源没有对象管理了,可以直接释放,如果不是0,说明还有对象在管理,所以不用处理。
2.循环引用问题
例如这样一个环形链表,当n1生命周期结束时发现,还有对象(n2的_next)在指向它,所以没有释放资源,当n2生命周期结束时发现还有对象(n1的_next)指向它,所以也没有释放资源。
也可以这样想:n1的资源什么时候释放,因为n2还在用呢,需要n2的资源释放掉,那么n2的资源什么时候释放,因为n1还在用呢,需要n1的资源释放掉。循环往复始终释放不了,所以导致内存泄漏。
3.weak_ptr处理逻辑
shared_ptr虽然很优秀,但依旧有缺陷——循环引用问题,所以就有了weak_ptr用来辅助shared_ptr来解决这个问题。
weak_ptr不⽀持RAII,也不⽀持访问资源,所以我们看⽂档发现weak_ptr构造时不⽀持绑定到资源,只⽀持绑定shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引⽤计数,那么就可以解决上述的循环引⽤问题。
weak_ptr没有重载operator*和operator->等,因为他不参与资源管理,那么如果他绑定的shared_ptr已经释放了资源,那么他去访问资源就是很危险的。weak_ptr⽀持expired检查指向的资源是否过期,use_count也可获取shared_ptr的引⽤计数,weak_ptr想访问资源时,可以调⽤lock返回⼀个管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是⼀个空对象,如果资源没有释放,则通过返回的shared_ptr访问资源是安全的。
如下示例:
#include<iostream>
#include<memory>
using namespace std;
int main()
{
shared_ptr<string> p(new string("left"));
weak_ptr<string> pw(p);
cout << pw.expired() << endl;
cout << pw.use_count() << endl;
auto p3 = pw.lock();
cout << *p3 << endl;
return 0;
}
四、shared_ptr的实现
计数器我们可以储存一个int*类型的指针来实现,然后需要一个指针来储存资源的地址。如下:
构造函数:我们是要用_ptr储存资源的地址空间所以需要传入一个指针,同时需要给_count开辟空间,并且设为1。
My_share_ptr(T* ptr)
:_ptr(ptr), _count(new int(1)){}
拷贝构造:只需要进行资源地址的拷贝和引用计数的拷贝,然后将_count++,表示该资源的管理者增加一个。
My_share_ptr(const My_share_ptr<T>& sptr)
{
_ptr = sptr._ptr;
_count = sptr._count;
(*_count)++;
}//也可以用初始化列表的方式来写
拷贝赋值:这个比较复杂它考虑到的因素有很多。
- 自己给自己拷贝赋值:对于这种情况,直接做一个特判后直接退出函数,不用做任何操作。
- 对于=的左操作对象:在被赋值前先处理一下现在手上的资源,即让_count--然后判断_count是否为0,如果是则进行资源的释放,如果不是则不用做任何处理。
- 进行赋值。
- 对于=的右操作对象:该对象被拷贝了一份说明对应资源的管理者增加,需要_count++。
My_share_ptr<T>& operator=(const My_share_ptr<T>& sptr) { if (sptr._ptr == _ptr) return *this; if (--(*_count) == 0) { delete _ptr; delete _count; cout << "Delete1()" << endl; } _ptr = sptr._ptr; _count = sptr._count; (*_count)++; return *this; }
对于其他运算符的重载比较简单,就不再多讲,我在下面的源码给出。
五、定制删除器
在上面我们用的都是delete来释放资源,但是不够灵活,不同的资源用不同的释放方式,比如数组用delete[ ],文件资源需要用fclose来释放,否则就会出问题。所以就有了定制删除器,可以通过传入仿函数的方式指定资源的释放方式。
删除器如果通过模板参数来实例化的话会显得十分别扭。我们期望使用传参的方式传入删除器。但是又面临一个问题,我们并不知道这个仿函数是什么类型,该怎么储存呢?因为删除器是做资源释放,所以可以确定它的返回类型是void,参数是T*,那么此时此刻包装器就起到了巨大的作用。
我们可以用function<void(T*)>类型接收并储存。同时把它做成一个全省参数,如下:
My_share_ptr(T* ptr, function<void(T*)> det= [](T* p) {delete p; })
:_ptr(ptr),_count(new int(1)),_det(det){}
当需要释放资源的时候不要用delete _ptr,而是用_det(_ptr)即可。 _count是int*类型正常使用delete释放。
六、源码
#include<iostream>
#include<string>
#include<functional>
#include<memory>
using namespace std;
template<class T>
class My_share_ptr
{
public:
My_share_ptr(T* ptr, function<void(T*)> det= [](T* p) {delete p; })
:_ptr(ptr),_count(new int(1)),_det(det){}
My_share_ptr(const My_share_ptr& sptr)
{
_ptr = sptr._ptr;
_count = sptr._count;
_det = sptr._det;
(*_count)++;
}
My_share_ptr& operator=(const My_share_ptr& sptr)
{
if (sptr._ptr == _ptr) return *this;
if (--(*_count) == 0)
{
_det(_ptr);
delete _count;
cout << "Delete1()" << endl;
}
_ptr = sptr._ptr;
_count = sptr._count;
(*_count)++;
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~My_share_ptr()
{
if (--(*_count) == 0)
{
_det(_ptr);
delete _count;
cout << "Delete2()" << endl;
}
}
private:
T* _ptr;
int* _count;
function<void(T*)> _det;
};
int main()
{
//My_share_ptr<int> p(new int(8));
//auto p2 = p;
//My_share_ptr<int> c = p;
My_share_ptr<int> p1(new int(8));
My_share_ptr<int> p2(new int(7));
My_share_ptr<int> p3=p1;
p1 = p2;
return 0;
}