为什么有智能指针?
在我们写代码时,经常会忘记释放掉动态开辟出来的内存,或者在我们的程序中,new和delete中间有如 throw/goto/return/break 这样引起执行流改变的语句时,就会造成没有释放资源,造成内存泄漏。
void Test1()
{
int* p=new int[100];
Test();
...
delete[] p;
调用的Test2()中如果有抛异常的话,就会无法释放p,造成内存泄漏,
这种情况就需要用智能指针来解决。
智能指针
所谓“智能指针”就是比一般的指针聪明,它可以智能/自动的释放掉指针所指向的动态资源空间。
那么这种智能指针是任何实现的呢?
在这里我们要提到一种思想
RAII(Resource Acquisition Is Initialization)
资源分配即初始化,定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。
所以智能指针其实就是一个类
智能指针(自动释放,可以像指针一样用)
1、自动释放
一个类里(构造,析构)
//RAII思想
2、像指针一样
通过运算符的重载实现(* 和->)
T& operator* ()
{
return *_ptr;
}
T* operator-> ()
{
return _ptr;
}
注意函数返回值类型
上面说了智能指针是什么,以及为什么存在这种智能指针,那么下面我们再来看看智能指针都有那些代表,以及它的发展
早期c++98 | auto_ptr | 管理权转移,带有缺陷 | |
---|---|---|---|
boost | scoped_ptr | 守卫指针 | 防拷贝 |
scoped_arrary | |||
shared_ptr | 共享指针 | 引用计数,缺陷循环引用 | |
shared_array | |||
weak_ptr | 弱指针 | 不单独存在,辅助解决shared_ptr的循环引用问题 |
一、模拟实现AutoPtr指针
// 智能指针--类(AutoPtr指针)
//首先是个类
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr)//构造
:_ptr(ptr)
{}
~AutoPtr()//析构,清理资源
{
if(_ptr)
{
printf("0x%p\n", _ptr);
delete _ptr;
}
}
// ap2(ap1)
AutoPtr(AutoPtr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = NULL;
}
// ap1 = ap2
AutoPtr<T>& operator=(AutoPtr<T>& ap)
{
if (this != &ap)
{
if(_ptr)
delete _ptr;
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
// 运算符的重载
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
struct AA
{
int _a1;
int _a2;
};
//写个函数对比测试一下
void Func ()
{
int* p1 = new int(2);
*p1 = 10;
// RAII
AutoPtr<int> ap1(new int(2));
*ap1 = 10; //ap1.operator*() = 20;
AA* p2 = new AA;
p2->_a1 = 10;
p2->_a2 = 20;
AutoPtr<AA> ap2(new AA);
ap2->_a1 = 10;
ap2->_a2 = 20;
}
void TestAutoPtr()
{
//Func();
int* p1 = new int(2);
int* p2 = p1;//没有释放内存,清理对象
// 深拷贝
AutoPtr<int> ap1(new int(1));
AutoPtr<int> ap2 = ap1;
AutoPtr<int> ap3(new int(2));
ap2 = ap3;
}
分析:
与普通指针的异同
对比普通指针和智能指针,智能指针可以实现*/->两个功能,同时可以自动清理资源
管理权的转移
我们可以看到这里就体现出了auto_ptr这种智能指针的特点,也是它的缺陷,当另一个指针指向时,之前的指针就对它没有任何权利了,变为空指针,权利全部给新的指针,一个auto_ptr,只能管理一块空间(就像一个人只能有一个男朋友/女朋友,分手后,前女友就和你没有关系了,珍惜现女友)
二、模拟实现Scoped_ptr指针
这个指针的特点是防拷贝
如何实现防拷贝
1.只声明不实现
2.私有
template<class T>
class ScopedPtr
{
public:
// RAII
ScopedPtr(T* ptr)
:_ptr(ptr)
{}
~ScopedPtr()
{
if(_ptr)
delete _ptr;
}
// 像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
// 防拷贝
// 1.只声明不实现
// 2.私有
private:
ScopedPtr(const ScopedPtr<T>& sp);
ScopedPtr<T>& operator=(const Scopedtr<T>& sp);
//将拷贝构造和operator=封在类里,设为私有
private:
T* _ptr;
};
void TestScopedPtr()
{
ScopedPtr<int> sp1(new int(1));
//ScopedPtr<int> sp2(sp1);错误使用
ScopedPtr<int> sp3(new int(2));
//sp1 = sp3;错误使用
}
三、Shared_ptr共享指针&Weak_ptr弱指针
(这两个指针要结合起来使用)
auto_ptr能共享空间,scoped_ptr不能拷贝,都很bug,但我们还有shared_ptr(当然它也有循环引用的问题,所以有弱指针辅助)
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr = NULL)
:_ptr(ptr)
,_refCount(new int(1))
{}
~SharedPtr()
{
if (--(*_refCount) == 0)
{
cout <<"释放了 "<< endl;
delete _ptr;
delete _refCount;
}
}
// sp2(sp1)
SharedPtr(const SharedPtr<T>& sp)
:_ptr(sp._ptr)
,_refCount(sp._refCount)
{
(*_refCount)++;
}
// sp2 = sp1
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
{
if (_ptr != sp._ptr)
{
if (--(*_refCount) == 0)
{
delete _ptr;
delete _refCount;
}
_ptr = sp._ptr;
_refCount = sp._refCount;
(*_refCount)++;
}
return *this;
}
T& operator*()
{...}
T* operator->()
{...}
int UseCount()
{
return *_refCount;
}
private:
T* _ptr;
int* _refCount;
};
上面的程序只是shared_ptr的部分功能的思想体现,不是真正的模拟shared_ptr指针,真正的shared_ptr在boost库里是很复杂的。
下面我们来说刚才写道的循环引用问题
struct ListNode
{
int _data;
SharedPtr<ListNode> _next;
SharedPtr<ListNode> _prev;
/*WeakPtr<ListNode> _next;
WeakPtr<ListNode> _prev;*/
~ListNode()
{
cout<<"~ListNode()"<<endl;
}
};
void TestSharedPtrRef()
{
SharedPtr<ListNode> cur(new ListNode);
SharedPtr<ListNode> next(new ListNode);
cout<<cur.UseCount()<<endl;//1
cout<<next.UseCount()<<endl;//1
cur->_next = next;
next->_prev = cur;
cout<<cur.UseCount()<<endl;//2
cout<<next.UseCount()<<endl;//2
}
这里的循环引用问题出现在了哪呢?
当我们调析构函数释放空间时,就出问题了
当我们要是放cur时,如果把cur直接释放,那么next的_prev就成了野指针。同理next节点也是一样的。 (其实就是当要释放cur节点时,先要清理掉它内部的指针,可是cur->next指向next,next->prev又指向cur,所以他们就成了一个死循环,一直就无法释放)
所以循环引用就是两个对象相互牵制了对方,从而导致两个对象谁都无法被释放,引发了内存泄露
运行程序时就会发现程序死循环了
所以为了解决这种问题,我们就要用weak_ptr我们只需要将节点的_next和_prev计数就好,不需要把shared_ptr的指针也计数,就可以解决问题
template<class T>
class WeakPtr
{
public:
WeakPtr()
:_ptr(NULL)
{}
WeakPtr(const SharedPtr<T>& sp)
:_ptr(sp._ptr)
{}
WeakPtr<T>& operator=(const SharedPtr<T>& sp)
{
_ptr = sp._ptr;
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};