The C++ Standard Library : A Tutoral and Reference 读书笔记之auto_ptr
这一部分会将要讨论一下auto_ptr.c++标准库提供auto_ptr作为一种智能指针,有了这种智能指针,就可以在异常被抛出的时候避免资源的泄漏.注意,auto_ptr只是智能指针的一种.因为事实上有多种智能指针存在.而auto_ptr只是被用来满足某种情形下的需求的.而对于其他一些情形,auto_ptr无能为力.所以在使用的时候 要格外注意.
auto_ptr的动机
通常,一个函数会按照下面的流程进行操作:
1.获取资源.
2.进行一些操作.
3.释放之前所获取的资源.
如果函数所获取的那些资源与本地对象绑定,它们就会在函数结束的时候被自动释放掉.因为本地对象的析构函数会被调用.但是如果资源被显式获取,并且没有与任何对象绑定,它们必须也被显式的释放掉.在使用指针的时候,资源通常是被显式管理的.
一种典型的像上面这样使用资源的方式就是使用new来创建和用delete来释放.
但是作释放用的delete操作往往被忽略(特别是函数有返回值的时候).当然,出现异常的时候,也同样会发生这种风险.因为异常有可能马上停止你函数的执行,而不去进行delete释放.结果就是内存泄漏,或者说得宽泛一些,是"资源泄漏".为了避免资源泄漏,通常需要函数能够捕获所有的异常.随之而来的就是,代码变得越来越复杂,冗长.
伴随着这种情况的发生,一种智能指针auto_ptr出现了.在任何时候,一旦指针本身被销毁,它所指向的数据也会被销毁.此外,因为智能指针是一个本地变量,不管函数到底是因为正常结束,还是因为非正常结束,auto_ptr所指向的数据都会被销毁.
有了auto_ptr,delete语句和catch语句就变得不那么必要了.auto_ptr和普通指针的使用方式基本相同,即用样用"*"表示它所指向的对象,同样用"->"来访问所指向对象的成员方法.但是有关指针的运算(比如指针的自加)不被支持(这也许是一个优点,因为指针运算也许会造成一些麻烦).
注意auto_ptr<>不允许你使用指派的方式来声明它所指向的对象,比如:
下面的操作是不允许的:
std::auto_ptr<ClassA> ptr2 = new ClassA; //ERROR
而应该采用显式的构造函数来初始化:
std::auto_ptr<ClassA> ptr1(new ClassA); //OK
通过auto_ptr来转换所有权
auto_ptr在语意上提供了对于所有全的严格约束.这意味着因为如果一个auto_ptr删除了它所指向的对象,那么这个对象将不会被其他任何对象所拥有.两个或以上的auto_ptr绝不能同时拥有一个对象.不幸的是,这种情况是有可能出现的.所以只能有程序员来确保这种情况不会发生.
那么在这种情况下,auto_ptr的拷贝构造函数如何工作呢?通常它会把一个对象的数据复制给另外一个对象,但是在这里,恰恰是这种行为会导致两个auto_ptr拥有相同对象这种情况的出现.解决方法很简单,但是却得出了一个重要结论:auto_ptr的拷贝构造函数和指派操作促成了auto_ptr对于对象所有权的一次移交.
看一下下面的代码:
//initialize an auto_ptr with a new object
std::auto_ptr<ClassA> ptr1(new ClassA);
//copy the auto_ptr
//- transfers ownership from ptr1 to ptr2
std::auto_ptr<ClassA> ptr2(ptr1);
第一句中,ptr1拥有new所创建的那个对象.然后第二句把对于对象的所有权从ptr1转移给了ptr2.所以在第二句之后,ptr2拥有new创建的那个对象的所有权,而ptr1则自动失去了对于那个对象的所有权.ClassA只会在ptr2被析构的时候被释放掉.
同样,对于一次指派操作也是如此.如下所示:
//initialize an auto_ptr with a new object
std::auto_ptr<ClassA> ptr1(new ClassA);
std::auto_ptr<ClassA> ptr2; //create another auto_ptr
ptr2 = ptr1; //assign the auto_ptr
//- transfers ownership from ptr1 to ptr2
注意,随着所有权的移交,之前失去对象所有权的那个auto_ptr就变成了一个空指针.
当然,还可以让这个失去所指的空指针重新指向一个对象,像下面这样:
std::auto_ptr<ClassA> ptr; //create an auto_ptr
ptr = std::auto_ptr<ClassA>(new ClassA); //OK, delete old object and own new
对于所指向的对象的所有权的变换为我们提供了auto_ptr的一种潜在用法.函数可以使用向其他的函数传递所有权.这包含两种情况.
1.传入函数的对象不再使用.即在auto_ptr作为参数传递的时候.在这种情况下,被调用的函数得到了传入的auto_ptr所指向的那个对象的所有权.因此,如果函数不再将对象的所有权转移出来,那么对象就会在函数的调用结束之后销毁.
2.函数要向外传出对象.当auto_ptr被返回的时候,所有权由被调用的函数转移给调用方.
由于auto_ptr总是牵扯到对象所有权变换的问题,所以在使用的时候一定要注意你是否真的要转换这个所有权.
你可能打算通过引用传递auto_ptr(s).但是这样做有可能最终你都不清楚所有权的归属了.总之,这不是一个好的决定或设计.
从auto_ptr的概念出发,你可能会觉得即使是传递常指针的话,对象的归属也会改变.如果这种情况可能发生,那将是非常危险的.因为人们总是不期待一个成为"常量"的东西发生改变.幸运的是,通过一些技术上的处理,这样做是不可能的.如:
1
#include
<
iostream
>
2 using namespace std;
3 int getValue(auto_ptr < int > );
4 int main()
5 {
6 const auto_ptr< int > myAutoPtr=auto_ptr< int >( new int );
7 * myAutoPtr = 10 ;
8 cout << getValue(myAutoPtr) << endl;
9
10 }
11 int getValue(auto_ptr < int > ptr)
12 {
13 return 10 * ( * ptr);
14 }
上面首先定义了常量的auto_ptr,这就表明,这个auto_ptr不能将自己所指对象的所有权传递给其他对象.因而调用函数getValue的时候如果传递了上面定义的auto_ptr,就会被编译器发现,并作为编译错误反馈回来:
2 using namespace std;
3 int getValue(auto_ptr < int > );
4 int main()
5 {
6 const auto_ptr< int > myAutoPtr=auto_ptr< int >( new int );
7 * myAutoPtr = 10 ;
8 cout << getValue(myAutoPtr) << endl;
9
10 }
11 int getValue(auto_ptr < int > ptr)
12 {
13 return 10 * ( * ptr);
14 }
错误: passing ‘const std::auto_ptr<int>’ as ‘this’ argument of ‘std::auto_ptr<_Tp>::operator std::auto_ptr_ref<_Tp1>() [with _Tp1 = int, _Tp = int]’ discards qualifiers
将auto_ptr(s)作为成员变量
当然,如果在类中使用auto_ptr的时候,你同样也可以避免资源泄漏这种问题的发生.如果你使用auto_ptr取代通常的指针的话,你将不再需要对那些对象进行手动的析构处理.同时,auto_ptr也能够有效避免因为初始化异常而导致资源泄漏这种情况的发生.因为析构总是在构造之后,所以如果在构造的时候发生异常,你往往不能够及时释放资源,进而导致资源泄漏情况的发生.
但是,在享用了auto_ptr带来的诸多好处的同时,你在处理拷贝构造函数和重载赋值操作的时候,要格外注意auto_ptr所有权变换的问题.当然,避免这类问题的最好方法就是使用常量指针.
有关auto_ptr(s)的滥用
auto_ptr能够满足一些需要,特别是容易发生资源泄漏的时候,它总能够发挥好的效果.但是在使用它的时候仍然需要注意,不要滥用.在使用的时候,要注意以下几点:
1.auto_ptr(s)不能够共享对于所指向对象的所有权.
2auto_ptr(s)不提供对于数组的支持.因为在释放资源的时候,auto_ptr使用的是delete而不是delete[].当然,对于数组而言,STL也有相应的处理办法,比如容器.
3.auto_ptr(s)不是智能指针的全部.注意它并不能解决一切问题.注意它的应用场景.
4.auto_ptr(s)不能满足容器中对象的操作要求.