1:智能指针的发展历史
<1>理解: 在动态内存分配中,为解决抛异常执行流跳转,导致内存泄漏的问题,产生了一个智能指针的类(smart pointer),智能指针是基于RAII的设计思想(资源分配即初始化,资源的有效期与持有对象的生命期严格绑定),期待它能像指针一样,智能的管理指针所指向的动态资源的释放。
<2>开始是auto_ptr(C++98):设计思想是管理权的转移,管理权在谁手上谁才有释放资源的权利,一块内存区域只能有一个管理员,通过赋值运算符赋值时,实际上也将管理权赋给了对方,auto_ptr有很大的缺陷,会造成野指针的问题,_owner会将错误延迟(比如越界问题,当在常量区时,会报错,在其他区域时不一定会报错,因为检查越界是按标志位处检查)这将是一个潜在的危险,最好不要使用auto_ptr;
<3> 后来由于 auto_ptr 会造成野指针的问题,有开源组织写了boost库(C++03之后的事),有三个指针指针:
scoped_ptr:守卫指针,防拷贝,是一种简单粗暴的方法;
shared_ptr:共享指针,引用计数,功能齐全;
weak_ptr:解决shared_ptr循环引用的缺陷问题,搭配shared_ptr使用;
<4>C++标准库参考boost库(C++11),设计了三个智能指针,unique_ptr=scoped_ptr , shared_ptr , weak_ptr
2:模拟实现
auto_ptr
设计思路:成员函数有_ptr和 _owner,构造函数时,最开始就将对象的_owner赋为true,在拷贝构造或赋值操作后,将_owner改为false,这就实现了管理权的转移,使一块空间只能有一个管理员。析构时,只有_owner为true才有资格释放空间。这个做法会造成野指针的问题。下图会做解释。最好不要使用auto_ptr。
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr = NULL)
:_ptr(ptr)
,_owner(true)
{}
AutoPtr(AutoPtr<T>& ap)
:_ptr(ap._ptr)
,_owner(ap._owner)
{
ap._owner = false;//ap的管理权到了this的手上,实现了管理权转移
}
AutoPtr<T>& operator=(AutoPtr<T>& ap)
{
if(_ptr != ap._ptr)//自身不给自身赋值
{
if(_ptr)
{
delete _ptr;
_ptr = ap._ptr;
ap._owner = false;
_owner = true;
}
}
return *this;
}
~AutoPtr()
{
if(_owner)
{
cout<<"~AutoPtr()"<<endl;
delete _ptr;
_ptr = NULL;
_owner = false;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
bool _owner; //管理员
};
//测试
void TestAutoPtr()
{
AutoPtr<int> ap1(new int(10));
//AutoPtr<int> ap2 = ap1;//编译器优化,调的是拷贝构造
AutoPtr<int> ap2(ap1);
//AutoPtr<int> ap3(new int(20));
//ap3 = ap1;
cout<<*ap1<<endl;
cout<<*ap2<<endl;
//cout<<*ap3<<endl;
}
改进版:拷贝构造和赋值操作后,将原来的指针置空,从而实现管理权的转移。
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr = NULL)
:_ptr(ptr)
{}
AutoPtr(AutoPtr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = NULL; //防止野指针
}
AutoPtr<T>& operator=(AutoPtr<T>& ap)
{
if(_ptr != ap._ptr)
{
if(_ptr)
{
delete _ptr;
_ptr = ap._ptr;
ap._ptr =NULL;
}
}
return *this;
}
~AutoPtr()
{
if(_ptr)
{
cout<<"~AutoPtr()"<<endl;
delete _ptr;
_ptr = NULL;
}
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
protected:
T* _ptr;
};
void TestAutoPtr()
{
AutoPtr<int> ap1(new int(10));
AutoPtr<int> ap2(ap1);
cout<<*ap2<<endl;
AutoPtr<int> ap3(new int(20));
ap3 = ap2;
cout<<*ap3<<endl;
//cout<<*ap1<<endl;//不能再输出,会出错
}
scoped_ptr
scoped_ptr(守卫指针)主要是为了防拷贝(no-copyable),为了防止多次析构同一指针所指向的 对象。防止拷贝主要有两个条件:(1) 设置保护限定符为私有。将拷贝构造和赋值运算符重载设置为私有,以免别人写了拷贝构造和赋值运算符重载。 (2) 对拷贝构造和赋值运算符重载进行只声明不定义。否则,编译器会自动合成拷贝构造和赋值运算符重载,达不到防止拷贝的目的。
template<class T>
class ScopedPtr
{
public:
ScopedPtr(T* ptr)
:_ptr(ptr)
{}
~ScopedPtr()
{
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
ScopedPtr(const ScopedPtr<T>&);
ScopedPtr<T>& operator=(const ScopedPtr<T>&);
protected:
T* _ptr;
};
void TestScopedPtr()
{
ScopedPtr<int> sp1 = new int(10);
ScopedPtr<int> sp2(sp1);
}
shared_pr
shared_ptr(共享指针)的思想是引用计数,功能齐全。引入指针变量_refCount,每增加一个指针指向同一空间,对_refCount++,只有当一个对指针指向该空间时,也就是_refCount=1时,才能释放空间。解决了多个指针对象指向同一块空间造成的内存泄漏问题。
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr = NULL)
:_ptr(ptr)
,_refCount(new int(1))
{}
SharedPtr(const SharedPtr<T>& sp)
:_ptr(sp._ptr)
,_refCount(sp._refCount)
{
++(*_refCount);
}
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;
}
~SharedPtr()
{
cout<<"~SharedPtr()"<<endl;
if (_ptr)
{
if(--(*_refCount) == 0)
{
delete _ptr;
delete _refCount;
}
}
}
int RefCount()
{
return *_refCount;
}
T* GetPtr()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
protected:
T* _ptr;
int* _refCount;
};
void TestSharedPtr()
{
SharedPtr<int> s1 = new int(10);
SharedPtr<int> s2 = new int(20);
//s2 = s1;
//cout<<*s2<<endl;
//cout<<*s1;
SharedPtr<int> s3(s2);
cout<<*s3<<endl;
}
这个SharedPtr是简化版本,只实现了最主要的部分,Boost库中的实现复杂的多,所考虑的问题更加全面。
weak_ptr
弱引用weak_ptr是为了配合shared_ptr而产生的,解决shared_ptr循环引用的问题。弱引用并不修改该对象的引用计数,意味着不对内存进行管理。弱引用能检测到所管理的对象是不是已经释放,从而避免非法访问。
//weak_ptr不增加引用计数,不能单独使用。
template<class T>
class WeakPtr
{
public:
WeakPtr()
:_ptr(NULL)
{}
WeakPtr(const SharedPtr<T>& sp)
:_ptr(sp.GetPtr())//获取SharedPtr的_ptr
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T* _ptr;
};
3:分析循环引用及解决方案
指针1的释放依赖于指针2的释放,指针2的释放依赖于指针1的释放。构成循环,不能释放,造成内存泄漏。
循环引用需要用到shared_ptr的use_count函数,用来获得当前的引用计数,可以使用boost库中的,也可以使用c++标准的,需要用到c++11,以下例子在vs2013下实现。
#include<iostream>
#include<memory>
#include<windows.h>
using namespace std;
//循环引用
struct ListNode
{
~ListNode()
{
cout << "~ListNode()" << endl;
}
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
};
void TestCycle()
{
shared_ptr<ListNode > prev(new ListNode);
shared_ptr<ListNode > next(new ListNode);
cout << "prev->count " << prev.use_count() << endl;
cout << "next->count " << next.use_count() << endl;
prev->_next = next;
next->_prev = prev;
cout << "prev->count " << prev.use_count() << endl;
cout << "next->count " << next.use_count() << endl;
}
int main()
{
TestCycle();
system("pause");
return 0;
}
解决:通过weak_ptr
struct ListNode
{
~ListNode()
{
cout << "~ListNode()" << endl;
}
weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;
};