C++标准库提供的auto_ptr是一种智能指针,帮助程序员防止“被异常抛出时发生资源泄露”。
auto_ptr的设计动机:
1. 获得一些资源
2. 执行一些动作
3. 释放获取的资源
如果一开始获取的资源被绑定于局部对象身上,当函数退出时,它们的析构函数被调用,从而自动释放这些资源,然而事情并不是总是如此顺利,如果资源是以显式手法获得,而且没有绑定在任何对象身上,那么必须显式手法释放。这样情形常常发生在指针身上。
例子:
void f()
{
ClassA * ptr = new ClassA;
……..
delete ptr;
}
如果我们忘记了释放资源或者是异常退出没有释放资源,这样就会给程序带来灾难。
如果使用智能指针,情况就会大不相同。这个智能指针应该保证,无论在何种情况下,只有自己被摧毁,就一定连带释放其所指的资源。
auto_ptr是这样一种指针:它是“它所指向的对象”的拥有者(owner)。所以,当身为对象拥有者的auto_ptr被摧毁时,该对象也将遭到摧毁。auto_ptr要求一个对象只能有一个拥有者,严禁一物二主。
例子:上面的例子改写为使用智能指针
#include<memory> //auto_ptr 包含在文件memory中
void f()
{
std::auto_ptr<ClassA> ptr(new ClassA);
…….
}
这样就不用担心忘记释放new的ClassA或异常退出了
注意: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的拷贝构造函数和赋值运算符会将对象拥有权移交出去。
例子:
std::auto_ptr<ClassA>ptr1(new ClassA);
std::auto_ptr<ClassA> ptr2(ptr1);
在第一个语句中,ptr1拥有了new出来的对象。在第二个语句中,拥有权由ptr1转交给ptr2。此后ptr2就拥有了那个new出来的对象,而ptr1不再拥有它。这样,对象就只会被delete一次——在ptr2被销毁的时候。
例子:赋值操作
std::auto_ptr<ClassA>ptr1(new classA);
std::auto_ptr<ClassA>ptr2;
ptr2=ptr1;
在这里,赋值操作将拥有权从ptr1转移至ptr2。
如果ptr2赋值之前正拥有另一个对象,则赋值操作发生时会调用delete,将该对象释放,再拥有ptr1指向的对象的拥有权。
例子:
std::auto_ptr<ClassA>ptr1(new ClassA);
std::auto_ptr<ClassA>ptr2(new ClassA);
ptr2=ptr1;
起点和终点:
拥有权的移转,使得auto_ptr产生一种特殊用法:某个函数可以利用auto_ptr将拥有权移交给另一个函数。
这种事情可能在两种情形下出现:
1. 某函数是数据的终点。如果auto_ptr以传值(by value)方式被当做一个参数传递给某函数。此时被调用端的参数获得了这个auto_ptr的拥有权,如果函数不再将它传递出去,它所指的对象就会在函数退出时被释放。
void sink(std::auto_ptr<ClassA>);
2. 某函数是数据的起点。当一个auto_ptr被返回,其拥有权便被转交给调用端了。
例子:
std::auto_ptr<ClassA>f()
{
std::auto_ptr<ClassA> ptr(newClassA);
…….
return ptr;
}
void g()
{
std::auto_ptr<ClassA>p;
for(int i=0;i<10;i++)
p=f();
}
问题:当智能指针以传值的方式传给函数做参数,但是只是想打印指针指向的值,而函数并不是指针的终点。
void bad_print(std::auto_ptr<T> p)
{
if(p.get()==NULL)
{
std::cout<< “NULL”;
}
else
{
std::cout<<*p;
}
}
std::auto_ptr<int>p(new int);
*p=42;
bad_print(p);
*p=18; //error p所指的对象已经被销毁
你可能会认为,将auto_ptr以传引用方式传递就万事大吉了。但是以引用传递智能指针你根本无法预知拥有权是否移交,这是非常糟糕的设计,应该全力避免。
解决:可以使用constant reference ,你无法变更任何constant reference的拥有权:
const std::auto_ptr<int> p(new int);
*p=42;
bad_print(p);
*p=18; //ok
总而言之,常数auto_ptr减小了“不经意转移拥有权”所带来的危险。只要一个对象通过auto_ptr传递,就可以使用常数型auto_ptr来终结拥有权转移,此后拥有权将不能再进行转移。
在这里,关键词const并非意味你不能更改auto_ptr所拥有的对象,而是意味你不能更改auto_ptr的拥有权。
例子:
const std::auto_ptr<int> p(new int);
std::auto_ptr<int> q(new int);
*p=42;
bad_print(p);
*p=*q;
p=p; //error
auto_ptr作为类成员之一
在class中使用auto_ptr,你可以因此避免遗失资源。如果你为auto_ptr而非一般指针作为成员,当对象被删除时,auto_ptr会自动删除其所指的成员对象,于是你也就不再需要析构函数了。此外,即使在初始化期间异常抛出,auto_ptr也可以帮助避免资源遗失。
注意:只有对象被完整构造成功,才有可能于将来调用其析构函数。
尽管你可以略过析构函数,却还是不得不自己写拷贝构造函数和赋值操作符。缺省情况下,这两个操作都会移交拥有权,这恐怕并非你所愿。为了避免拥有权的意外移交,如果你的auto_ptr在整个生命期间内都不必改变其所指对象的拥有权,你可以使用const auto_ptr。
auto_ptr的错误运用:
1. auto_ptr之间不能共享拥有权
2. 并不存在针对数组而设计的auto_ptr
auto_ptr不可以指向数组,因为auto_ptr是透过delete而非delete[]来释放其所拥有的对象。
3. auto_ptr不满足STL容器对其元素的要求
因此请绝对不要将auto_ptr作为标准容器的元素。
auto_ptr实例:
#include<iostream>
#include<memory>
using namespace std;
template<classT>
ostream&operator<< (ostream & strm,const auto_ptr<T> & p)
{
if(p.get()==NULL)
strm<< “NULL”
else
strm<<*p;
return strm;
}
int main()
{
auto_ptr<int> p(new int(42));
auto_ptr<int> q;
cout<< “after initialization:”<<endl;
cout<< “p:”<<p<<endl;
cout<< “q:”<<q<<endl;
q=p;
cout<< “after assigning auto pointers:”<<endl;
cout<< “p:”<<p<<endl;
cout<< “q:”<<q<<endl;
*q+=13;
p=q;
cout<< “after change andreassignament:”<<endl;
cout<< “p:”<<p<<endl;
cout<< “q:”<<q<<endl;
}
程序输出:
after initialization:
p: 42
q: NULL
after assigning auto pointers:
p: NULL
q: 42
after changeand reassignament:
p: 55
q: NULL
注:
auto_ptr::element_type
auto_ptr所拥有对象的型别
auto_ptr::get()
返回auto_ptr所指对象的地址
如果auto_ptr未指向任何对象,返回null指针
T *auto_ptr::release()
放弃auto_ptr原先所拥有的对象的拥有权
返回auto_ptr原先拥有对象的地址
如果auto_ptr原先未拥有任何对象,返回null指针