1.智能指针的发展历史
智能指针的产生:在c语言中存在自动回收机制,而在C++中并没有这个机制程序员每次 new 出来的内存都要手动 delete。程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见。所以有人根据RAII的思想(RAII:维基百科的解释RAII要求,资源的有效期与持有资源的对象的生命周期严格绑定,即由对象的构造函数完成资源的分配,同时由析构函数完成资源的释放)设计了一系列的智能指针。
2.auto_ptr
最开始智能指针就是auto_ptr从名字上我们就可以看出来,先根据库中的函数模拟实现一个auto_ptr
如下:
template<class T>
class Auto_Ptr
{
public:
Auto_Ptr(T* ptr) :
_ptr(ptr)
{};
Auto_Ptr()
:_ptr(NULL)
{}
Auto_Ptr(Auto_Ptr<T> &t):
_ptr(t._ptr)
{
t._ptr = NULL;
}
Auto_Ptr<T>& operator=(Auto_Ptr<T>& t)
{
if (_ptr != t._ptr)
{
if (_ptr)
{
delete _ptr;
}
_ptr = t._ptr;
t._ptr = NULL;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return &_ptr;
}
~Auto_Ptr()
{
if (_ptr)
{
delete _ptr;
_ptr = NULL;
}
}
protected:
T* _ptr;
};
然后进行测试
void TestAuto_ptr()
{
Auto_Ptr<int> p2(new int(10));
cout << *p2 << endl;
cout << &p2 << endl;
Auto_Ptr<int> p3(p2);
cout << *p2 << endl;
cout << *p3 << endl;
cout << &p3 << endl;
}
结果:
发现在进行拷贝构造之后p2的值为空,测试赋值运算符之后也是一样的结果,这就是auto_ptr的设计来源,auto_ptr所做的事就是管理权转移,
即每次只有一个指针指向资源空间,每次有其他指针时就将管理权转移,原来指针就会置空,所有如果调用原函数就会导致程序崩溃,这就是auto_ptr最大的缺陷,还有一些其他缺陷:
1.auto_ptr不能共享管理权。
2.auto_ptr不能指向数组。
3.auto_ptr不能作为容器的成员。
4.不能通过赋值初始化auto_ptr。
总的来说auto_ptr存在重大缺陷,所以尽量避免使用auto_ptr。
3.scoped_ptr
因为auto_ptr存在重大缺陷,所以又产生了另一种智能指针scoped_ptr,这种指针的设计思想非常简单,因为拷贝与赋值都存在问题,所以scoped_ptr就 直接禁止进行拷贝与赋值,这是一种防卫编程的原理。然而这种做法也存在一点瑕疵。
首先代码如下:
template<class T>
class Scoped_ptr
{
public:
Scoped_ptr(T* ptr) :
_ptr(ptr)
{};
Scoped_ptr():
_ptr(NULL)
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return &_ptr;
}
~Scoped_ptr()
{
if (_ptr)
{
delete _ptr;
_ptr = NULL;
}
}
protected:
Scoped_ptr(Scoped_ptr<T> &t);
Scoped_ptr<T>& operator=(Scoped_ptr& t);
T* _ptr;
};
测试:
void TestScoped_ptr()
{
Scoped_ptr<int> p1(new int(10));
cout << *p1 << endl;
cout << &p1 << endl;
Scoped_ptr<int> p2(p1);
}
结果:
结果表明scoped_ptr已经无法进行拷贝构造。但是毕竟在实际情况中还是有很多需要用到拷贝构造和赋值的时候,所以这种智能指针只能只在有限的地方使用。
4.shared_ptr
shared_ptr指针中采用的是引用计数的方法,这种方法已经可以说是比较完善的,进行模拟实现:
template<class T>
class Shared_ptr
{
public:
Shared_ptr(T* ptr) :
_ptr(ptr),
_refCount(new int(1))
{};
Shared_ptr() :
_ptr(NULL),
_refCount(NULL)
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return &_ptr;
}
Shared_ptr(Shared_ptr<T>& s):
_ptr(s._ptr),
_refCount(s._refCount)
{
(*_refCount)++;
};
Shared_ptr<T>& operator=(Shared_ptr<T>& s)
{
if (_ptr != s._ptr)
{
if (--(*_refCount) == 0)
{
delete _ptr;
_ptr = NULL;
delete _refCount;
_refCount = NULL;
}
_ptr = s._ptr;
_refCount = s._refCount;
(*_refCount)++;
}
return *this;
};
~Shared_ptr()
{
if (--(*_refCount) == 0)
{
delete _ptr;
_ptr = NULL;
delete _refCount;
_refCount = NULL;
}
}
protected:
T* _ptr;
int* _refCount;
};
测试:
void Testshared_ptr()
{
Shared_ptr<int> s(new int(10));
Shared_ptr<int> s1(s);
}
结果:
从结果可以看出,这种引用计数的方法非常的实用,可以说这种指针已经解决了大部分的问题,但是并即使这种方法已经接近完美了,但是还是存在问题的,就是关于循环引用的问题。
5.Shared_ptr的循环引用问题
如图:
当有一个节点它里面包含有指针,并且当有两个类似节点构成了一个循环引用时我们就会发现当我们要调用析构函数就会变成,因为总有一个指针指向节点,而且两个节点任何一个都无法被释放,所以会陷入死循环,而这样也就造成了内存泄漏这一严重问题。
6.weak_ptr
首先weak_ptr不能算是一个智能指针,它是因为shared_ptr存在循环引用的问题,所以在使用时们要配合shared_ptr来使用,而且这种指针不会自主辨别循环引用,所以我们要自己自己决定使用的实际,在判断会出现时,我们就调用weak_ptr。