目录
1. 什么是RAII?
2. auto_ptr
3. boost
4. unique_ptr
5. 定制删除器
7. week_ptr
1. 什么是RAII?
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内
存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在
对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做
法有两大好处:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效
// 利用RAII思想写出的智能指针模板template <class T>
class SmartPtr
{SmartPtr(T* ptr):_ptr(ptr);{}~SmartPtr(){delete _ptr;} T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};
2. auto_ptr
在C++98中就提供了一种类似于智能指针的指针。
auto_ptr的原理是:将管理权转移的思想
具体实现如下:
#pragma once
namespace bit
{template<class T>class auto_ptr{//构造函数auto_ptr(T* ptr):_ptr(ptr){}//转移资源auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr){ap._ptr = nullptr;}//赋值auto_ptr<T>& operator=(auto_ptr<T>& ap){if (this != &ap){if (_ptr){delete _ptr;}_ptr = ap._ptr;ap._ptr = nullptr;}return *this;}//析构函数~auto_ptr(){if (_ptr){delete _ptr;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}
这样有一种缺陷就是赋值的指针将会变成空指针,这种效果也遭受了很多学习C++语法人的吐槽,以至于很多公司禁止使用该指针。
这种“智能指针”不能满足我们的要求,因此在C++11出现之前,广泛的C++使用者就聚齐在一起,形成了一个社区:boost
3. boost
什么是boost?
而我们的智能指针就出自于此,较为著名的智能指针包括:scoped_ptr 、shared_ptr 、week_ptr
而C++出台的unique_ptr、shared_ptr、week_ptr都出自于此。
4. unique_ptr
什么是unique_ptr?
通过名字我们可以发现它被称为独一无二的指针,也就是说明它是无法被复制的,而它的底层也确实如此。
通过它的拷贝构造,我们可以发现它没有对象构造和赋值构造,这也就保证了该指针指向的空间只有它一个指向。
那么我们来观察它的底层是如何实现的:
using namespace std;
namespace bit
{template<class T>struct default_delete{void operator()(T* ptr){delete ptr;ptr = nullptr;}};template<class T,class D = default_delete<T>>class unique_ptr{public://构造函数unique_ptr(T* ptr):_ptr(ptr){}unique_ptr(unique_ptr<T>& ap) = delete;//赋值unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;//析构函数~unique_ptr(){if (_ptr){cout << "delete ptr" << endl;D d;d(_ptr);}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}
在它的底层中将其中两个构造函数加上了delete,在C++11中新增加了delete和default关键字,如果一个函数后加上delete,表示无法默认生成。那么就表明如果不显式写这两个构造函数,那么就能够实现unique这个特点。
至于这里还存在一个叫D的类型,它表示我们存储的指针以什么形式释放
这里我们就称为定制删除器
5. 定制删除器
如果我们传入的值是通过new实现出来的,那么我们在释放的时候就需要通过delete来进行释放
如果是new [],那么我们就需要delete[]来进行释放,所有我们的关键点就是如何在析构的时候释放空间,这里我们就涉及到了定制删除器。
定制删除器顾名思义就是对不同的步骤定制了不同的解决方案,
比如如果传入的值是通过new来实现的,那么就需要使用delete
如果是通过malloc来实现的,那么就需要使用free
比如unique_ptr,我们就可以发现它不仅仅是传入一个T参数,还需要传入一个D参数,这个D参数就是我们的定制删除器,就可以在析构中调用D所形成的对象来释放我们的指针。
6. shared_ptr
有一种情况:多个指针管理同一块区域,那么我们这块区间是什么时候进行释放?
这里我们就用到了shared_ptr,那么shared_ptr是如何来实现区间的释放呢?
其实是采用了计数的原理,就好比老师让最后一个离开教室的把们关上一样,如果最后只剩下一个指针管理这块区域,那么当对象销毁时,这块空间也随之释放。
shared_ptr的特点:
1. 通过计数来实现多个shared_ptr对象管理同一块资源
2. 当对象被销毁时,将每个资源所指向的计数-1,
3. 如果当前资源的计数为0,则将其释放,如果不为0,则说明还有指针指向当前资源
那么它的基本实现是怎样呢?
#pragma once
namespace bit
{template<class T>class shared_ptr{void Release(){if (--(*_count) && _ptr){delete _ptr;_ptr = nullptr;delete _count;_count = nullptr;}}//构造函数shared_ptr(T* ptr):_ptr(ptr),_count(new int(1)){}//转移资源shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr),_count(sp._count){++(*_count);}//赋值shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){Release();_ptr = sp._ptr;_count = sp._count;*(_count)++;}return *this;}//析构函数~shared_ptr(){Release();}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get(){return _ptr;}private:T* _ptr;int* _count;};
}
这里最为关键的就是Release函数,通过该函数我们可以得知是否所指向的资源要释放,这也是shared_ptr的灵魂所在。
7. week_ptr
那么shared_ptr就是完美了吗?
在不整花活的情况下,使用shared_ptr是完全足够的,但是总会有特殊情况
如果我们要实现这样的情况,会发生什么呢?
我们发现在程序终止时,没有打印出析构函数里面的字符串,因为我们可以发现没有当p1、p2出了作用域后,没有进行销毁,产生了内存泄漏。
那么这是怎么发生了呢?
那么当p1、p2释放后,计数值还是1,_next的释放取决于_prev释放,而_prev的释放又取决于_next,那么这样相互依赖的关系被称为循环引用。
那么我们是如何来避免进行循环引用的呢?,这里C++11就提出了一个解决方案:引入了一个新指针-- week_ptr,由于我们的计数的由于_next和_prev指向了空间,导致我们的计数+1,又因为我们想让_next和_prev指向空间,因为我们可以当它指向空间时。当前空间不计数,这也是week_ptr的原理,也就是小弟帮大哥办事。
namespace bit
{template <class T>class week_ptr{public:week_ptr(const T* ptr):_ptr(ptr){}week_ptr(const week_ptr<T>& wp):_ptr(wp._ptr){}week_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}void operator = (const shared_ptr<T>& sp){_ptr = sp.get();}private:T* _ptr;};
}
只干事没有实权,这就是小弟应该做的事!!!