【auto_ptr 的由来】
先看下面的代码。
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#ifdef _DEBUG
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif
void ABC()
{
throw "Exception!";
}
void Func()
{
int* pi = new int(31);
ABC(); //如果ABC() 抛出异常,delete得不到执行,内存泄露
delete pi;
}
int _tmain(int argc, _TCHAR* argv[])
{
_CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
try
{
Func();
}
catch(...)
{
}
return 0;
}
动态分配了资源pi,如果在ABC()函数的执行中出现异常,控制跳转到main的catch中,执行不到delete语句,造成资源泄漏。
Detected memory leaks!
Dumping objects ->
d:\workspace\myprojects2010\smartpointer\smartpointer.cpp(20) : {105} normal block at 0x00348120, 4 bytes long.
Data: < > 1F 00 00 00
Object dump complete.
auto_ptr 是C++标准库提供的一种智能指针(模板类),主要用来解决此类问题,使动态内存和异常之间更加友好。Auto_ptr 保证当异常抛出时,分配的对象能自动销毁。用auto_ptr将 int 包装一下,如果ABC()抛出异常,pi会被自动销毁(pi 其实是一个局部对象,其析构函数中会释放它绑定的动态分配对象,析构函数执行的时机,是在控制权被转移到catch之前)。
void Func()
{
//No suitable constructor exsits to convert int* to auto_ptr<int>
//auto_ptr<int> pError = new int;
auto_ptr<int> pi(new int);
*pi = 31;
ABC();
}
注意此处pi不是一个裸指针,而是一个auto_ptr的对象,它重载了操作符&, *, -> 所以能够像使用指针一样去使用它。
在实例化对象pi时,只能使用括号的方式,不能使用 = 。(有的IDE可能允许这种方式,但是运行会出错)下文有解释。
【auto_ptr 详解】
1. auto_ptr 在构造时,获取对某个对象的所有权(ownership),在析构时释放其拥有的那个对象。一个萝卜一个坑,两个auto_ptr不能同时拥有同一个对象。
int* p = new int(1);
auto_ptr<int> ap1(p);
auto_ptr<int> ap2(p);
ap1和ap2都认为p是归它管的,析构时都试图delete p,两次删除同一个对象在C++标准中是未定义的,在VS2010中运行会出错。
2. 不能用auto_ptr来管理数组指针。因为析构函数中释放资源使用的是delete,而释放一个数组需要delete []。
int* pa = new int[10];
pa[0] = 0;
auto_ptr<int> ap(pa);
(ap.get())[1] = 1;
3. 构造函数的explicit关键字,阻止从一个裸指针隐式转换成auto_ptr类型。
这种形式是错误的!
int* p = new int(2);
auto_ptr<int> pa = p;
补充:explicit 关键字的用法。
class People
{
public:
explicit People(int a)
{
m_age = a;
}
private:
int m_age;
};
用三种方式创建People的实例
People p1(10); //类变量声明方式
People* p2 = new People(20); //People类的指针变量
//People p3 = 30;
第三种方式进行了一次隐式类型转换,编译器自动将对应于构造函数参数类型的数据转换为了该类的对象,添加了explicit关键字后,这种方式被禁止。
4. auto_ptr 不是引用计数型的智能指针,它对于裸指针有完全的占有性。拷贝或赋值的源对象将失去对裸指针的所有权,变成空的,目标对象将先释放原来拥有的对象,再接管新的裸指针。
这种情况比较隐蔽的情形是将auto_ptr作为函数参数按值传递。在函数的作用域内会产生一个局部对象来接收传入的auto_ptr(拷贝构造),这样,传入的实参就失去了对原指针的所有权,而局部对象在函数退出时会delete掉这个指针。
void f(auto_ptr<int> ap)
{
cout << *ap;
}
auto_ptr<int> ap1(new int(5));
f(ap1);
ap1变成empty了。最好避免将auto_ptr 作为函数参数按值传递。
5. auto_ptr 不具有值语义,所以不能被用在stl标准容器中。
值语义是指符合以下条件的类型,假设有类A
A a1;
A a2(a1);
A a3;
a3 = a1;
那么 a2 == a1, a3 == a1。显然auto_ptr不符合。而STL标准容器中需要用到大量的拷贝复制操作,操作的类型必须符合这个条件。
6. 辅助函数
get():返回auto_ptr 拥有的对象指针。
release():返回拥有的指针,将拥有的裸指针置为null。不会释放对象,仅归还所有权。
reset():接收所有权。如果接收所有权的auto_ptr已经拥有某对象,需先释放。如不带参数,释放内部管理的内存。
【参考】
http://www.cppblog.com/SmartPtr/archive/2007/07/05/27549.html