C++ primer plus 16.2节介绍了auto_ptr,该模板类在C++11中已弃用,目前已被shared_ptr代替。
auto_ptr又叫做智能指针,用于管理动态内存分配的用法。
为什么要有auto_ptr?
首先看一个例子,
void remodel(string &str){
string *ps = new string(str);
...
str = ps;
return;
}
此例子有什么缺陷呢?
函数结束的时候没有释放内存,这导致了内存的泄露,指针ps会被释放,但是其指向的内存并没有被释放。一般来说,解决new导致的内存泄露的方法是在return之前使用delete.
但凡是涉及到“别忘了”的操作,都不是最佳的操作,因为一定会有开发者忘记,或者在不经意间删除或者注释掉了这些代码。
就算是没有忘记,也会因为一些别的操作导致内存无法释放,例如以下的代码:
void remodel(string &str)
{
string *ps = new string(str);
...
if(xxx){
throw exception();
}
str = *ps;
delete ps;
return;
}
在delete执行之前,如果执行过程中产生了异常并抛出,则后续的指令都不会执行,这仍将导致内存泄露。
如何解决
首先,我们知道,C++创建一个对象有两种方式,new和直接声明。new一个对象将在堆中动态分配内存来创建一个对象,而直接声明将会在栈内存中创建对象。
(所谓的堆,是指程序内部除了栈和静态存储区之外的"自由空间(free store)",堆内存用于在程序运行时分配对象。)
前者的生命周期是全局的,后者则受到作用域限制,也就是说对于前者产生的对象,如果不去主动释放它,则该对象所占据的内存会一直存在,如果没有指针可以指向它,则会导致内存泄露。
而直接声明的对象,在一个类对象过期的时候,析构函数会在一个对象过期的时候自动执行从而将该对象释放。
不妨这样思考,如果指针能被封装为一个对象,则这个包含指针的对象过期的时候调用其析构函数,将该指针指向的对象delete,不就能够把它指向的内存也释放了吗?
于是auto_ptr便诞生了,auto_ptr是一个模板类,这样便可以创建指向任何类型的智能指针。
auto_ptr的设计和使用
template<class T>class auto_ptr{
public:
explicit auto_ptr(T* p = 0) throw();
};
throw()意味着构造函数不引发异常,因此请求T类型的auto_ptr将获得一个指向T类型的auto_ptr.
这样便定义好了一个智能指针,其实非常简单,就是在构造的时候将普通的指针放入了auto_ptr模板类中
使用的时候
auto_ptr<double> ap(new double);//在构造函数中填入new出来的对象
*ap = 23.3;//ap对象的地址其实就是new出来的double对象的地址,所以可以用指针取值的方式赋值。
所以,改造文章开头的remodel函数:
void remodel(string &str){
auto_ptr<string> ps(new string(str));
...
if(xxx) throw exception();
str=*ps;
return;
}
这里,便不再需要使用delete了。
shared_ptr和unique_ptr
C++11摒弃了auto_ptr,主要是由于auto_ptr无法进行指针的传递。auto_ptr必须显式地初始化,即auto_ptr<string> ps(new string("sdf"))
,
为了多个指针指向一个new出的对象,C++11提出了shared_ptr,
shared_ptr中增加了引用计数,有N个指向一个同一个内存区域的shared_ptr时,它们共同维护一个引用计数器。
shared_ptr的大概实现如下:
template<typename T> class shared_ptr{
private:
T* _ptr;//指向的对象
int *_count;//引用计数器
public:
...
shared_ptr():_ptr((T*)0),_count(0){}
~shared_ptr(){
if(_ptr && --*count==0){
delete _ptr;
delete _count;
}
}
};
当智能指针的析构函数启用时,只有引用计数为0的时候,才会进行析构。