很多人听说过标准auto_ptr智能指针机制,但并不是每个人都天天使用它。这真是个遗憾,因为auto_ptr优雅地解决了C++设计和编码中常见的问题,正确地使用它可以生成健壮的代码。本文阐述了如何正确运用auto_ptr来让你的代码更加安全——以及如何避免对auto_ptr危险但常见的误用,这些误用会引发间断性发作、难以诊断的bug。
1.为什么称它为“自动”指针?
auto_ptr只是众多可能的智能指针之一。许多商业库提供了更复杂的智能指针,用途广泛而令人惊异,从管理引用的数量到提供先进的代理服务。可以把标准C++ auto_ptr看作智能指针的Ford Escort(elmar注:可能指福特的一种适合家居的车型):一个简易、通用的智能指针,它不包含所有的小技巧,不像专用的或高性能的智能指针那么奢华,但是它可以很好的完成许多普遍的工作,它很适合日常性的使用。auto_ptr所做的事情,就是动态分配对象以及当对象不再需要时自动执行清理。这里是一个简单的代码示例,没有使用auto_ptr所以不安全:
// 示例 1(a): 原始代码 // void f() { T* pt( new T );/*...更多的代码...*/
delete pt;
}我们大多数人每天写类似的代码。如果f()函数只有三行并且不会有任何意外,这么做可能挺好的。但是如果f()从不执行delete语句,或者是由于过早的返回,或者是由于执行函数体时抛出了异常,那么这个被分配的对象就没有被删除,从而我们产生了一个经典的内存泄漏。
能让示例1(a)安全的简单办法是把指针封装在一个“智能的”类似于指针的对象里,这个对象拥有这个指针并且能在析构时自动删除这个指针所指的对象。因为这个智能指针可以简单的当成一个自动的对象(这就是说,它出了作用域时会自动毁灭),所以很自然的把它称之为“智能”指针:
// 示例 1(b): 安全代码, 使用了auto_ptr
// void f() { auto_ptr<T> pt( new T );/*...更多的代码...*/
} // 酷: 当pt出了作用域时析构函数被调用,
// 从而对象被自动删除现在代码不会泄漏T类型的对象,不管这个函数是正常退出还是抛出了异常,因为pt的析构函数总是会在出栈时被调用。清理会自动进行。
最后,使用一个auto_ptr就像使用一个内建的指针一样容易,而且如果想要“撤销”资源,重新采用手动的所有权,我们只要调用release():
// 示例 2: 使用一个 auto_ptr
// void g() { T* pt1 = new T; // 现在,我们有了一个分配好的对象// 将所有权传给了一个auto_ptr对象
auto_ptr<T> pt2( pt1 );// 使用auto_ptr就像我们以前使用简单指针一样
*pt2 = 12; // 就像 "*pt1 = 12;" pt2->SomeFunc(); // 就像 "pt1->SomeFunc();"// 用get()来获得指针的值
assert( pt1 == pt2.get() );// 用release()来撤销所有权
T* pt3 = pt2.release();// 自己删除这个对象,因为现在
// 没有任何auto_ptr拥有这个对象 delete pt3;} // pt2不再拥有任何指针,所以不要
// 试图删除它...ok,不要重复删除最后,我们可以使用auto_ptr的reset()函数来重置auto_ptr使之拥有另一个对象。如果这个auto_ptr已经拥有了一个对象,那么,它会先删除已经拥有的对象,因此调用reset()就如同销毁这个auto_ptr,然后新建一个并拥有一个新对象:
// 示例 3: 使用reset()
// void h() { auto_ptr<T> pt( new T(1) );pt.reset( new T(2) );
// 删除由"new T(1)"分配出来的第一个T} // 最后,pt出了作用域,
// 第二个T也被删除了
正确使用std::auto_ptr
1, auto_ptr类
auto_ptr是一个模板类,定义如下:
template <typename Type>class auto_ptr {...};
它存储的是一个指向Type的指针。
顾名思义,auto_ptr是一种智能指针,它包含一个动态分配内存的指针,并在它生命周期结束的时候,销毁包含的指针所指向的内存。
例1:
void f()
{
Type* pt(new Type);
//一些代码...
delete pt;
}这样的代码很常见,但它有可能造成内存泄露。首先你用了new,你就要记得用delete,但即使你记住了用delete,还是会出问题。如果f()在执行delete pt之前,就抛出了异常,函数返回了。那么这个分配的对象就没被删除。
使用auto_ptr,很优雅的解决了这些问题。
例2:
void f()
{
auto_ptr<Type> pt(new Type);
//一些代码...
}现在的代码,不会泄露Type类型的对象。不管是函数正常结束,还是抛出异常结束,都会调用pt的析构函数,从而删除分配的对象。
2, auto_ptr构造函数
构造函数1:explicit auto_ptr(Type* _Ptr = 0) throw( );
auto_ptr<int> pt; //包含一个int*的指针,并初始化为NULL
auto_ptr<int> pt(new int(123)); //包含一个int*的指针,并初始化为123的地址
auto_ptr<int> pt = new int(123); //error!构造函数声明为explicit
构造函数2:auto_ptr(auto_ptr<Type>& _Right) throw( );
int* ptr = new int();
auto_ptr<int> pt1(ptr); //构造函数1
auto_ptr<int> pt2(pt1); //将pt1的使用权转给pt2,注意pt1指向NULL了
//pt1调用了本身的release()函数,将内部指针地址传给pt2
构造函数3:template<typename Other>
auto_ptr(auto_ptr<Other>& _Right) throw( );
声明这样一个拷贝构造函数的目的,就是为了派生类指针能转换成基类的指针。
例:
class Base { };
class Derived : public Base { };
auto_ptr<Derived> pDerived(new Derived);
auto_ptr<Base> pBase(pDerived); //让这样的代码能通过编译器
其本质是为了让,auto_ptr类内部的Derived*转换为Base*
构造函数4:auto_ptr(auto_ptr_ref<Type> _Right) throw( );
//暂略
3, auto_ptr成员函数
成员函数1:Type* get( ) const throw( );
获得包含指针的地址
int* ptr = new int(123);
auto_ptr<int> pt(ptr);
assert(pt.get() == ptr); //相等,指向同一地址
成员函数2:Type* release( ) throw( );
返回包含指针的地址,并将包含指针设为NUll
string* pstr = new string("hello");
auto_ptr<string> pt(pstr);
pt.release(); //不在指向string对象
//此时,pt.get()等于NULL
delete pstr; //应该手动删除pstr指向的内存块
成员函数3:void reset(Type* _Ptr = 0);
double* pdouble1 = new double(3.14);
double* pdouble2 = new double(1.23);
auto_ptr<double> pt1(pdouble1);
pt1.reset(pdouble2); //将删除pt1所指向的内存块就是pdouble1指向的那块
//此时,pt.get()等于pdouble2
cout << *pdouble1; //error,pdouble已经是野指针了
4, 使用总结
1,auto_ptr存储的指针应该为NULL或者指向动态分配的内存块。
2,auto_ptr存储的指针应该指向单一物件(是new出来的,而不是new[]出来的)。
3,两个auto_ptr对象不会同时指向同一块内存块。要明白2个auto_ptr对象赋值会发生什么。
4,千万不要把auto_ptr对象放在容器中。
5,当将auto_ptr作为函数参数时,最好声明为const auto_ptr<T>&(by const ref).当函数返回值可以简单的传值(by value).
shared_ptr
unique_ptr