RAII资源分配即初始化,定义一个类来封装资源的分配和释放,在构造 函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。
智能指针的引入:
由于return ,throw等关键字的存在,导致顺序执行流的错乱,不断的进行跳转,使开辟的空间
看似被释放,而实际上导致内存的泄露。
例如以下两个例子:
void Test1()
{
int *p1 = new int(1);
if (1)
{
return;
}
delete p1;
}
void DoSomeThing()
{ //...
throw 2 ;
//...
}
void Test2 ()
{
int* p1 = new int(2);
//...
try
{
DoSomeThing();
}
catch(...)
{
//delete p1 ;
throw;
}
//...
delete p1 ;
}
以上两个例子,看似new与delete结合使用,但是实际上已经导致内存的泄露,为了解决以上问题,就需要在写此类代码时需要多加小心,但是对于大量的代码开发,想要注意就很难了。
于是乎就引入了智能指针:
所谓智能指针就是智能/自动化的管理指针所指向的动态资源的释放。
c++库函数中的智能指针为(auto_ptr,unique_ptr,shared_ptr);
Boost库函数中的智能指针为(auto_ptr,scoped_ptr,shared_ptr)。
接下来就一起来了解一下智能指针的发展历史。
1、在vc6.0之前的版本中auto_ptr的模拟实现方式
template<class T>
class OldAutoPtr
{
public:
OldAutoPtr(T *ptr)
:_ptr(ptr)
, _owner(true)
{}
//拷贝构造
OldAutoPtr(OldAutoPtr& ap)
:_ptr(ap._ptr)
,_owner(ap._owner)
{
ap._owner = false;//权限的转让
}
//运算符重载
OldAutoPtr& operator=(OldAutoPtr& ap)
{
if (ap._ptr != _ptr)//自赋值和指向同一份空间
{
if (_ptr)
{
delete _ptr;
}
_ptr = ap._ptr;
_owner = ap._owner;//权限的转让
ap._owner = false;
}
return *this;
}
//析构函数
~OldAutoPtr()
{
if (_owner)//只删除拥有权限的指针
{
delete _ptr;
}
}
public:
T& operator *()
{
return *_ptr;
}
T* operator ->()
{
return _ptr;
}
private:
T* _ptr;
bool _owner;//权限拥有者
};
评价:
看似最初版本的auto_ptr已经很接近了原声指针的使用,多个指针都可以指向一份空间,而且释放的同时不会导致同一份空间的多次释放。但是在下面的情境中却发生了致命的危害:
void TestOldAutoPtr()
{
OldAutoPtr<int> ap1(new int(1));
if (true)
{
OldAutoPtr<int> ap2(ap1);
//OldAutoPtr<int> ap3(new int(2));
//ap3 = ap2;
}
//ap1将析构的权限给予了ap2,ap1,ap2指向同一份空间,ap2在出了if作用域之后ap2对象释放,进而导致ap1也被释放。
//但是在if作用域之外,又对ap1(ap2)指向的空间进行简引用,导致程序崩溃,如果不使用的话则会造成指针的悬挂(野指针)。
*ap1 = 10;
}
针对以上的状况,在之后的版本中对auto_ptr进行了完全的权限转移,转移之后该指针不能使用。
2、现在的版本auto_ptr的实现
template<typename T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{}
AutoPtr(AutoPtr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = NULL;//权限转移
}
AutoPtr<T>& operator=(AutoPtr<T> & ap)
{
if (this != &ap)//自赋值
{
if (_ptr)
{
delete _ptr;
}
_ptr = ap._ptr;
ap._ptr = NULL;//权限转移
}
return *this;
}
~AutoPtr()
{
if (_ptr != NULL)
{
delete _ptr;
_ptr = NULL;
}
}
public:
T& operator * ()//没有参数
{
return *_ptr;
}
T* operator->()//没有参数
{
return _ptr;
}
private:
T* _ptr;
};
评价:
这种实现方式很好的解决了old的版本特殊场景的野指针问题,但是它与原生指针的差别更大,由于实现了完全的权限转移,所以导致在拷贝构造和赋值之后只有一个指针可以使用,而其他指针都置为NULL,使用很不方便,而且还很容易导致对于NULL指针的截引用,导致程序崩溃,其危害也是比较大的。
3、针对以上的问题,在Boost库中引入了简单粗暴的解决方法scoped_ptr,直接不允许其拷贝构造和赋值(ps:新的C++11标准中叫做unique_ptr)
解决办法:将赋值和拷贝构造函数只声明,不实现,切记,将其声明为protected或者private以免在类的外部对其进行破环性处理,同时也是提高了代码的安全性。
template<typename T>
class ScopedPtr
{
public:
ScopedPtr(T* ptr)
:_ptr(ptr)
{}
~ScopedPtr()
{
if (_ptr != NULL)
{
delete _ptr;
}
}
protected://将其声明为protected或者private不能声明为public
ScopedPtr(ScopedPtr<T>& sp);
ScopedPtr<T>operator=(ScopedPtr<T>&sp);
public:
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T *_ptr;
};
评价:
在一般的情况下,如果不需要对于指针的内容进行拷贝,赋值操作,而只是为了防止内存泄漏的发生,该只能指针完全可以满足需求。
4、允许拷贝构造和赋值的shared_ptr模拟实现
解决办法:
通过为每个空间多开辟4个字节做为引用计数器,在拷贝构造、赋值、析构时用计数器来解决。
template<typename T>
class SharedPtr
{
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _pCount(new int(1))
{}
SharedPtr(const SharedPtr<T>& sp)
{
_ptr = sp._ptr;
_pCount = sp._pCount;
(*_pCount)++;
}
SharedPtr<T>operator=(SharedPtr<T> sp)
{
swap(_ptr, sp._ptr);
swap(_pCount, sp._pCount);
return *this;
}
~SharedPtr()
{
_Realse();
}
public:
void _Realse()
{
if (--(*_pCount) == 0)
{
delete _ptr;
delete _pCount;
}
}
public:
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
int* _pCount;
};
评价:
简化版智能指针SharedPtr看起来不错,但是实际上存在以下问题:
1)循环引用
2)定置删除器
循环引用
template<class T>
struct Node
{
public:
~Node()
{
cout << "delete:" << this << endl;
}
public:
shared_ptr<Node> _prev;
shared_ptr<Node> _next;
/*weak_ptr<Node> _prev;
weak_ptr<Node> _next;*/
};
void test_round()//循环引用
{
shared_ptr<Node> cur(new Node());
shared_ptr<Node> next(new Node());
cout << "连接前:" << endl;
cout << "cur:" << cur.use_count() << endl;
cout << "next:" << next.use_count() << endl;
cur->_next = next;
next->_prev = cur;
cout << "连接后:" << endl;
cout << "cur:" << cur.use_count() << endl;
cout << "next:" << next.use_count() << endl;
/*shared_ptr<Node> cur(new Node());
weak_ptr<Node> wp1(cur);*/
}
解决办法:
使用一个弱引用智能指针(weak_ptr)来打破循环引用(weak_ptr不增加引用计数)
template<class T>
struct Node
{
public:
~Node()
{
cout << "delete:" << this << endl;
}
public:
//shared_ptr<Node> _prev;
//shared_ptr<Node> _next;
weak_ptr<Node> _prev;
weak_ptr<Node> _next;
};
void test_round()//循环引用
{
shared_ptr<Node> cur(new Node());
shared_ptr<Node> next(new Node());
cout << "连接前:" << endl;
cout << "cur:" << cur.use_count() << endl;
cout << "next:" << next.use_count() << endl;
cur->_next = next;
next->_prev = cur;
cout << "连接后:" << endl;
cout << "cur:" << cur.use_count() << endl;
cout << "next:" << next.use_count() << endl;
/*shared_ptr<Node> cur(new Node());
weak_ptr<Node> wp1(cur);*/
}
定置删除器
在shared_ptr中只能处理释放new开辟的空间,而对于malloc,以及fopen打开的文件指针不能处理,为了能够全面的处理各种各样的指针,所以提出了定制删除器,而其实现则是通过仿函数(通过对()运算符的重载)来实现。
1)模拟实现的解决
template<typename T,typename D>
class SharedPtr
{
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _pCount(new int(1))
{}
SharedPtr(T* ptr, D del)
:_ptr(ptr)
, _pCount(new int(1))
, _del(del)
{}
SharedPtr(const SharedPtr<T, D>& sp)
{
_ptr = sp._ptr;
_pCount = sp._pCount;
(*_pCount)++;
}
SharedPtr<T, D>operator=(SharedPtr<T, D> sp)
{
swap(_ptr, sp._ptr);
swap(_pCount, sp._pCount);
return *this;
}
~SharedPtr()
{
_Realse();
}
public:
void _Realse()
{
if (--(*_pCount) == 0)
{
_del(_ptr);
delete _pCount;
}
}
public:
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
int* _pCount;
D _del;
};
template<typename T>
struct DeafaultDel
{
void operator()(T* ptr)
{
cout << "DeafaultDel:" << ptr << endl;
}
};
template<typename T>
struct Free
{
void operator()(T*ptr)
{
cout << "Free:" << ptr << endl;
}
};
template<typename T>
struct Fclose
{
void operator()(T* ptr)
{
cout << "Fclose:" << ptr << endl;
fclose(ptr);
}
};
//测试代码
void TestDelete()
{
int *p1 = (int*)malloc(sizeof(int));
SharedPtr<int,Free<int>> sp1(p1);
FILE* p2 = fopen("test.txt", "r");
SharedPtr<FILE, Fclose<FILE>> sp2(p2);
}
测试结果
2)系统shared_ptr的解决
struct Free
{
void operator()(void * ptr)
{
cout << "Free:" << ptr << endl;
free(ptr);
}
};
struct Fclose
{
void operator ()(FILE* ptr)
{
cout << "Fclose" << ptr << endl;
fclose(ptr);
}
};
//测试代码
void test_shared_ptr_delete()
{
int *p1 = (int*)malloc(sizeof(int));
shared_ptr<int> sp1(p1,Free());
FILE* p2 = fopen("test.txt", "r");
shared_ptr<FILE> sp2(p2,Fclose());//崩溃
}
测试结果
总结:
1、如果需要使用智能指针的话,scoped_ptr完全可以胜任。在非常特殊的情况下,例如对STL容器对象,应该只使用shared_ptr,任何情况下都不要使用auto_ptr.
2、使用时尽量局部化,因为其是通过调用其析构函数来实现资源的回收。
转载于:https://blog.51cto.com/10788311/1760242