auto_ptr所要做的工作就是获得一个已经被动态分配的对象,并且当对象不再需要时执行自动地清除,避免内存泄露。看下面两段代码:
(1)
void f()
{
T* pt(new T);
/*.....其他代码......*/
delete pt;
}
(2)
void f()
{
auto_ptr<T> pt(new T);
/*......其他代码......*/
}
(1)中的代码如果从来没有执行delete语句,可能是因为执行delete之前函数就返回了,或者也可能是因为在执行函数的时候出现了异常,那么在函数开头进行的内存分配的对象将不会删除,于是代码中就出现了一个典型的内存泄露。而(2)中无论函数是否是由于正常退出或者抛出异常而没有执行delete语句,在上面的代码中都不会存在T对象的内存泄露问题,因为当堆回卷时,总能调用pt的析构函数。因此内存清理工作也将自动进行。
①所有权
当你对auto_ptr进行拷贝时,对象的所有权将自动地从源auto_ptr传递到目标auto_ptr中;如果目标auto_ptr已经拥有了一个对象,那么这个对象将首先被释放。在拷贝完成之后,只有在目标auto_ptr中才拥有原来在源auto_ptr中的指针,并在需要的时候对这个指针将执行删除操作,而源 auto_ptr将会被设置为空状态,并且不在能访问曾经拥有的对象。例如:
void f()
{
auto_ptr<T> pt1(new T);
aut0_ptr<T> pt2;
pt2 = pt1;//现在,pt2将拥有指针,而pt1则没有
pt1->func();//错误,这是一个空指针
}
②封装指针数据成员
template<class T>
class C
{
public:/*......*/
protected:/*......*/
private:
auto_ptr<T> ptr;
}
这样做有两个好处:一是使我们在类C的析构函数中不用进行内存清理工作;二是如果在类中使用的是裸指针数据成员,而不是auto_ptr数据成员,那么你就必须在类中提供自己的析构函数,拷贝构造函数和拷贝赋值运算符函数,因为在这些函数的默认版本中将会进行错误清理。
③auto_ptr与异常安全性
看下面两个函数:
(1)
string f()
{
string result;
result = "hello";
cout << "baby" << endl;//操作一
return result;//操作二
}
(2)
auto_ptr<string> f()
{
auto_ptr<string> result = new string;
*result = "hello";
cout << "baby" << endl;//操作一
return result;//操作二,这个操作依赖所有权的转移,而这个过程是不会抛出异常的
}
对于(1)中返回string对象肯定会调用string的拷贝构造函数,如果在拷贝构造函数中出现了失败,那么由于函数f()已经完成了它的所有操作,因此返回的结果肯定会被丢失。这就会导致操作一完成了而操作二没有完成,但是为了保证函数的行为时原子的:即使在函数中抛出了异常,那么这两个操作也是要么都执行,要么都不执行。所以需要用到(2)的方法,在这种方法中没有构造函数的调用,而是依赖于所有权的转移,而这个过程是不会抛出异常的。
④常量auto_ptr的惯用法
常量auto_ptr是永远不会失去所有权的,事实上,在常量auto_ptr上能进行的所有操作智能是调用operator*()来进行解引用操作,或者调用operator->()和调用get()来查询所包含的指针值。这就意味着,如果想表示一个auto_ptr永远都不要失去所有权,那么我们就可以使用常量auto_ptr惯用法。
const auto_ptr<T> pt1 (new T);
auto_ptr<T> pt2(pt1);//
auto_ptr<T> pt3;
pt3 = pt1;//非法的
pt1.release();//非法的
pt1.reset(new T);//非法的
⑤注意事项:
a.auto_ptr的构造函数时显式的,这意味着不存在从指针到auto_ptr对象的隐式类型转换
auto_ptr<double> pd;
double *p = new double;
pd = p;//不允许
pd = auto_ptr<double>(p);//允许
auto_ptr<double> pauto(p);//允许
b.auto_ptr模板使用的是delete,而不是delete[],因此只能与new一起使用,而不能与new[]一起使用。
auto_ptr<int> pi(new int [200]);//不允许
c.不要把auto_ptr放到标准的容器中,因为auto_ptr并不能完全满足容器的模板参数类型需求定义,auto_ptr的副本并不是等价的。
vector<auto_ptr<T>> v;//不允许
附auto_ptr源代码:
template<class T>
class auto_ptr
{
public:
explicit auto_ptr(T *p = 0);//显示类型转换
template<class U>
auo_ptr(auto_ptr<U>& rhs);//以任何兼容的auto_ptr作为一个新的auto_ptr的初值
~auto_ptr();
template<class U>
auto_ptr<T>& operator=(auto_ptr<U>& rhs);//以任何兼容的auto_ptr作为赋值动作的右端
T& operator*() const;
T* operator->() const;
T* get() const;//返回原生指针
T* release();//撤回原生指针的拥有权,并返回其值
void reset(T *p = 0);//将拥有的指针删除,并承担p的拥有权
private:
T *pointee;
template<class U>
friend class auto_ptr<U>;//让所有的auto_ptr classes都成为另一个auto_ptr的friends
};
template<class T>
inline auto_ptr<T>::auo_ptr(T *p):pointee(p){}
template<class T>
template<class U>
inline auto_ptr<T>::auo_ptr(auto_ptr<U> &rhs):pointee(rhs.release()){}
template<class T>
template<class U>
inline auto_ptr<T>::operator =(auto_ptr<U> &rhs)
{
if(this != &rhs)
reset(rhs.release());
return *this;
}
template<class T>
inline T& auto_ptr<T>::operator *() const
{
return *pointee;
}
template<class T>
inline T* auto_ptr<T>::operator ->() const
{
return pointee;
}
template<class T>
inline T* auto_ptr<T>::get() const
{
return pointee;
}
template<class T>
inline T* auto_ptr<T>::release()
{
T *oldPointee = pointee;
pointee = 0;
return oldPointee;
}
template<class T>
inline void auto_ptr<T>::reset(T *p)
{
if (pointee != p)
{
delete pointee;
pointee = p;
}
}