auto_ptr的设计动机
函数的操作经常是依以下模式进行:
1.获取一些资源。
2.执行一些操作。
3.释放所获取的资源。
如果一开始获取的资源被绑定在局部对象上,当函数退出时,它们的析构函数被调用,从而自动释放这些资源。然而事情并不总是如此顺利,如果资源是以显示(explicitly)获得,而且没有被绑定在任何对象上,那就需要以显示手法释放。这种情形常常发生在指针上。
如下一个运用new和delete来产生和销毁对象的典型例子:
void f()
{
ClassA* ptr = new ClassA; //create an object explicitly
... //perform some operations
delete ptr; //clean up (destroy the object explicitly)
}
如果在delete对象之前,函数就返回了或出现异常而退出,那么函数就根本不会调用delete语句。这很可能造成资源泄露,而防止这种资源泄露的常见办法就是捕获所有异常,但这会使程序代码变得非常复杂和累赘。
如果使用智能指针,情形就会大不一样。这个智能指针应该保证,无论在何种情形下,只要自己被摧毁,就一定连带释放其所指资源。而由于智能指针本身就是区域变量,所以无论是正常退出,还是异常退出,它一定会被销毁。auto_ptr正是这种指针。
auto_ptr是这样一种指针:它是“它所指对象”的拥有者。所以,当身为对象拥有者的auto_ptr被摧毁时,该对象也将遭到摧毁。auto_ptr要求一个对象只能有一个拥有者,严禁一物二主。
#include <memory>
void f()
{
//create and initialize anauto_ptr
std::auto_ptr<ClassA> ptr(new ClassA);
... //perform some operations
}
通过使用智能指针,不再需要delete,也不需要捕获异常。不过注意:
auto_ptr<>不允许使用一般指针惯用的赋值初始化方式。你必须直接使用数值来完成初始化:
std::auto_ptr<ClassA> ptr1(new ClassA); //OK
std::auto_ptr<ClassA> ptr2 = new ClassA; //ERROR
auto_ptr拥有权的转移
auto_ptr所界定的是一种严格的拥有权观念。也就是说,由于一个auto_ptr会删除其所指对象,所以这个对象绝对不能同时被其他对象“拥有”。绝对不应该出现多个auto_ptr同时拥有一个对象的情况。
这个条件会使auto_ptr的copy构造函数和assignment操作符的实现有所不同:copy构造函数和assignment操作符会将对象拥有权交出去。
//initialize anauto_ptr with a new object
std::auto_ptr<ClassA> ptr1(new ClassA);
//copy theauto_ptr
//- transfers ownership fromptr1 toptr2
std::auto_ptr<ClassA> ptr2(ptr1);
此时ptr2指向原ptr1所指对象,而ptr1现在指向NULL。而且要注意:只有auto_ptr可以拿来当做另一个auto_ptr的初值,普通指针是不行的:
std::auto_ptr<ClassA> ptr; //create an
auto_ptr
ptr = new ClassA; //ERROR
ptr = std::auto_ptr<ClassA>(new ClassA); //OK, delete old object
// and own new
拥有权的转移,使得auto_ptr产生一种特殊用法:某个函数可以利用auto_ptr将拥有权转交给另一个函数。这种事情可能发生在两种情形下:
- 某函数是数据的终点。如果auto_ptr以传值方式被当做一个参数传递给某函数,就有这种情况。此时被调用端的参数获得这个auto_ptr的拥有权,如果函数不再将它传递出去,它所指的对象就会在函数退出时被删除。
- 某函数是数据的起点。当一个auto_ptr被返回,其拥有权便被转交给调用者了。
auto_ptr作为成员之一
如果你以auto_ptr而非一般指针作为成员,当对象被删除时,auto_ptr会自动删除其所指的成员对象,于是你也就不再需要析构函数了。此外,即使在初始化期间抛出异常,auto_ptr也可以帮助避免资源泄漏。注意,只有当对象被完全构造成功,才有可能于将来调用其析构函数。这就造成了资源泄漏的隐患:如果第一个new成功了,第二个new却失败了,就会造成资源泄漏。例如:
class ClassB {
private:
ClassA* ptr1; //pointer members
ClassA* ptr2;
public:
//constructor that initializes the pointers
//- will cause resource leak if secondnew throws
ClassB (ClassA val1, ClassA val2)
: ptr1(new ClassA(val1)), ptr2(new ClassA(val2)) {
}
//copy constructor
//- might cause resource leak if second new throws
ClassB (const ClassB& x)
: ptr1(new ClassA(*x.ptr1)), ptr2(new ClassA(*x.ptr2)) {
}
//assignment operator
const ClassB& operator= (const ClassB& x) {
*ptr1 = *x.ptr1;
*ptr2 = *x.ptr2;
return *this;
}
~ClassB () {
delete ptr1;
delete ptr2;
}
...
};
使用auto_ptr,你可以轻松避免这场悲剧:
class ClassB {
private:
const std::auto_ptr<ClassA> ptr1; //auto_ptr members
const std::auto_ptr<ClassA> ptr2;
public:
//constructor that initializes theauto_ptrs
//- no resource leak possible
ClassB (ClassA val1, ClassA val2)
: ptr1 (new ClassA(val1)), ptr2(new ClassA(val2)) {
}
//copy constructor
//- no resource leak possible
ClassB (const ClassB& x)
: ptr1(new ClassA(*x.ptr1), ptr2(new ClassA(*x.ptr2)) {
}
//assignment operator
const ClassB& operator= (const ClassB& x) {
*ptr1 = *x.ptr1;
*ptr2 = *x.ptr2;
return *this;
}
//no destructor necessary
//(default destructor letsptr1 andptr2 delete their objects)
...
};
auto_ptr的错误运用
(1)auto_ptr之间不能共享使用权
一个auto_ptr千万不能指向另一个auto_ptr(或其他对象)所拥有的对象。否则,当第一个指针删除该对象后,另一个指针突然间指向了一个已被销毁的对象,那么,如果再使用那个指针进行读写操作,就会引发一场灾难。
(2)并不存在针对array而设计的auto_ptr
auto_ptr不可以指向array,因为auto_ptr是透过delete而非delete[]来释放其所拥有的对象。
(3)auto_ptr绝非一个“四海通用”的智能指针
并非任何使用智能指针的地方,都适用auto_ptr。特别注意,它不是引用计数型指针——这种指针保证,如果有一组智能型指针指向一个对象,那么当且仅当最后一个智能指针被销毁时,该对象才会被销毁。
(4)auto_ptr不满足STL容器对其元素的要求
auto_ptr并不满足STL容器对于元素的最基本要求,因为在拷贝和赋值动作之后,原本的auto_ptr和新产生的auto_ptr并不相等。
auto_ptr类别的实作示范
// util/autoptr.hpp
/* classauto_ptr
*- improved standard conforming implementation
*/
namespace std {
//auxiliary type to enable copies and assignments (now global)
template<class Y>
struct auto_ptr_ref {
Y* yp;
auto_ptr_ref (Y* rhs)
: yp(rhs) {
}
};
template<class T>
class auto_ptr {
private:
T* ap; //refers to the actual owned object (if any)
public:
typedef T element_type;
//constructor
explicit auto_ptr (T* ptr = 0) throw()
: ap(ptr) {
}
//copy constructors (with implicit conversion)
//- note: nonconstant parameter
auto_ptr (auto_ptr& rhs) throw()
: ap (rhs. release()) {
}
template<class Y>
auto_ptr (auto_ptr<Y>& rhs) throw()
: ap(rhs.release()) {
}
//assignments (with implicit conversion)
//- note: nonconstant parameter
auto_ptr& operator= (auto_ptr& rhs) throw() {
reset(rhs.release());
return *this;
}
template<class Y>
auto_ptr& operator= (auto_ptr<Y>& rhs) throw() {
reset(rhs.release());
return *this;
}
//destructor
~auto_ptr() throw() {
delete ap;
}
//value access
T* get() const throw() {
return ap;
}
T& operator*() const throw() {
return *ap;
}
T* operator->() const throw() {
return ap;
}
//release ownership
T* release() throw() {
T* tmp(ap);
ap = 0;
return tmp;
}
//reset value
void reset (T* ptr=0) throw(){
if (ap != ptr) {
delete ap;
ap = ptr;
}
}
/* special conversions with auxiliary type to enable copies
and assignments
*/
auto_ptr(auto_ptr_ref<T> rhs) throw()
: ap(rhs.yp) {
}
auto_ptr& operator= (auto_ptr_ref<T> rhs) throw() { //new
reset(r.yp);
return *this;
}
template<class Y>
operator auto_ptr_ref<Y>() throw() {
return auto_ptr_ref<Y>(release());
}
template<class Y>
operator auto_ptr<Y>() throw() {
return auto_ptr<Y>(release());
}
};
}