注:源码剖析在代码注释展现
了解智能指针
-
头文件 #including< memory >
-
版本
VC版本自动指针auto_ptr VS版本自动指针auto_ptr boost库的六种智能指针 局部指针: scoped_ptr、scoped_array 共享计数器指针: shard_ptr、scoped_array 弱指针: weak_ptr 侵入式引用计数指针: intrusive_ptr C++11参考boost实现的: week_ptr、shared_ptr、unique_ptr(对应scoped_ptr)
分析智能指针
- RAII
- 智能
- 指针
RAII思想
智能指针的产生,就是程序员希望实现 利用对象的生命周期来控制程序资源,
在构造时获取资源,这样使资源的访问在对象的生命周期内始终保持有效,
在析构时释放资源,这样使程序员不需要再显示的释放资源。
智能
智能就体现在,它使用构造方法和析构方法对空间进行自动获取和释放。这就是RAII思想的实践。
所以不论是最早的auto_ptr自动指针还是最重要的shared_ptr引用计数指针,智能指针的出发点都是资源的自动管理。
指针
说他是指针,是因为重载了 *取地址和->指向符。
使用
int* ptr = new int(10);
auto_ptr<int> pa(ptr);
cout<< *pa <<endl;
string *ptr = new string("abcxyz");
auto_ptr<string> pa(pstr);
cout<< pa->size() <<endl;
VC版本的自动指针模拟源码剖析
VC版本的自动指针,是最早的使用RAII思想的代码,在一段时间中风靡全球,虽然技术已经有所精进,但它仍不失为我们学习的一个好的模板样例。
它使用用_Owns成员控制空间拥有权,优点是使用RAII对资源进行自动管理,缺点是只是对拥有权进行标识,但实际没有拥有权的对象也可以对空间进行操作。
template<class _Ty>
class auto_ptr
{
public:
auto_ptr(_T* p = 0):_Owns(p != 0),_Ptr(p){}
~auto_ptr()
{
//由于这样的析构方法和浅拷贝,在拷贝构造和赋值的时候就需要尤其注意拥有权的转移,
//不然很容易造成一块空间被释放两次,或空间得不到释放(内存泄漏)
if(_Owns)
delete _Ptr;
}
auto_ptr(const auto_ptr<_Ty>& Y):_Owns(Y._Owns),_Ptr(Y.release())
{//通过release完成对Y拥有权的释放,同时将Ptr返回}
_Ty* release()const
{
//在构造函数传参是,我们通常不希望对其做出改变,传入常引用,
//在常的前提下如何保证可以对拥有权的改变呢?VC的做法如下:
((auto_ptr<_Ty>*)this)->_Owns = false;
//我们也可以通过对拥有权成员_Owns附加mutable关键字突破const的限制。达到对拥有权改变
return _Ptr;
}
auto_ptr& operator=(const auto_ptr<_Ty>& Y)
{
if(this != &Y)
{
//为保证同一块空间不被释放多次和内存泄漏
//需要先判断是不是同一块空间,对应不同的处理方法
if(_Ptr != Y._Ptr)
{
if(_Owns)
delete _Ptr;
_Owns = Y._Owns;
}
//如果this和Y不是同一块空间,那么即代表this只需要拥有权转移即可
else if(Y.Owns)
{
_Owns = true;
}
//修改_Ptr的指向并释放Y拥有权
_Ptr = Y.release();
}
return *this;
}
_Ty* operator->()const
{return _Ptr;} //->指向符完成调用成员函数或成员,需要返回地址
_Ty& operator*()const
{return *_Ptr;} //*完成解引用或对值得修改,需要返回值得引用
private:
bool _Owns; //mutable bool _Owns;
_Ty _Ptr;
};
VS版本的自动指针模拟源码剖析
很明显,VC版本的缺点就是拥有权的转移仅仅只是表面现象,实际上转移后的对象还是可以对空间进行操作的。这时不合理的。
VS版本得自动指针将VC版本得拥有权转化为对成员指针的赋空,失去管理权的对象不能再对原来的空间进行操作了。
这一点,与C++11右值引用应用中的移动构造还是相同的。
template<class _Ty>
class auto_ptr
{
private:
_Ty* _Ptr;
public:
auto_ptr(_Ty* p = 0):_Ptr(p){}
~auto_ptr(){delete _Ptr;}
//构造函数通过release函数完成对Y._Ptr的返回和Y拥有权的释放(ptr赋空)
~auto_ptr(auto_ptr<_Ty>& Y):_Ptr(Y.release()
{//这里显然是不能使用const传参了,为什么,难道设计师就不知道嘛?}
_Ty* release()
{
//使用临时变量的思想也不失为RAII,这在scoped_ptr中有更加突出的表现
_Ty* tmp = Y._Ptr;
Y._Ptr = nullptr;
return tmp;
}
auto_ptr& operator=(auto_ptr<_Ty> Y)
{
if(this != &Y)
{
//release完成Y对象的空间释放和转移
//使用reset完成了this空间的释放和拥有权转移
reset(Y.release());
}
return *this;
}
void reset(_Ty* p)
{
//加上条件,防止同一块空间相互赋值
if(_Ptr != p)
_Ptr = nullptr;
_Ptr = p;
}
T*
}
auto_ptr当前的实现只能对单一对象进行管理,没有能力对数组空间进行管理。
如:int* ptr = new int[10];
就不能使用auto_ptr接收ptr构造对象
其原因就是:
- RAII析构函数在析构时,智能delete _Ptr释放单个空间
- 没有重载new[ ]
boost库的智能指针可以让解决这种问题。
boost库的六种智能指针
scoped_ptr:
scoped 英文释义:局部、范围
它从命名上就展示了它的性质: 拥有权不能发生转移。
操作上不难想到: 它将 赋值语句、拷贝构造、operator!=()、operator==()这几个拥有权可能会发生改变的函数全部私有化了。
所以用法上,我们也需要记住,是不能拷贝构造和赋值的。
而且scoped_ptr重载了auto_ptr传参的构造方法。
int *ptr = new int(10);
scoped_ptr<int> s1(ptr);
scoped_ptr<int> s2(ptr);
scoped_ptr<int> s3(s1); 错误!
s2 = s3; 错误!
auto_ptr<int> s4(ptr);
scoped_ptr<int> s5(s4); 正确!
值得一说的是,scoped_ptr的优点之一在于它的reset( )函数上,
scoped_ptr不能发生拥有权的转移,可是它通过reset()方法实现了空间的赋值。
它的源码是这样的:
typedef scoped_ptr this_type;
void reset(T* p = 0)
{
//通过创建一个临时对象指向p空间,然后再与this空间进行交换
//交换完成之后,临时对象被释放,this空间得到改变,p对象指向的空间也没有发生改变
//通过这种方法,突破了拥有权,实现了空间的重置
//而这种借助临时对象自动释放的原理,也不失为一种RAII思想,体现了它的智能。
this_type(p).swap(*this);
}
void swap(scoped_ptr &b)
{
T* tmp = p.px;
b.px = px;
px = tmp;
}
int main()
{
int *ptr = new int(10);
scoped_ptr<int> p(ptr);
int* qtr = new int(20);
p.reset(qtr);
return 0;
}
scoped_array:
命名上就告诉我们,它与scoped_ptr大致相同,也是不能进行拥有权的转移。
将拷贝构造、赋值语句等都进行了私有化。
但与之不同的是:
重载了new[](不是单纯的new),在堆上分配动态数组,为动态数组提供代理,保证可以正确释放内存。
但是!!!
它算是智能数组吧!没有重载*解引用和->指向符。
也没有begin、end等迭代器的操作。
shared_ptr
重中之重,来了!
有一句话“ shard_ptr 在现在甚至未来,都是最先进的”。