首页 > 代码库 > C++智能指针--auto_ptr指针
C++智能指针--auto_ptr指针
auto_ptr是C++标准库提供的类模板,头文件<memory>,auto_ptr对象通过初始化指向由new创建的动态内存,它是这块内存的拥有者,一块内存不能同一时候被分给两个拥有者。当auto_ptr对象生命周期结束时,其析构函数会将auto_ptr对象拥有的动态内存自己主动释放。
即使发生异常,通过异常的栈展开过程也能将动态内存释放。auto_ptr不支持new数组。
auto_ptr的出现,主要是为了解决“有异常抛出时发生内存泄漏”的问题。例如以下的简单代码是这类问题的一个简单演示样例。
int* p = new int(100); try { doSomething(); cout << *p << endl; delete p; } catch(exception& e) { }
当doSomething();部分抛出异常。将导致指针p所指向的空间得不到释放而导致内存泄露。auto_ptr的引入攻克了这类问题。
初始化auto_ptr对象的方法?
auto_ptr构造时取得某个对象的全部权,在析构时释放该对象。
我们实际上是创建一个auto_ptr<Type>类型的局部对象,该局部对象析构时,会将自身所拥有的指针空间释放。所以不会有内存泄露。
auto_ptr<int> p(new int(1));//推荐 //或 int* np = new int(1); auto_ptr<int> p(np);
1) 构造函数
1] 将已存在的指向动态内存的普通指针作为參数来构造
int* p = new int(33);
auto_ptr<int> api(p);
2] 直接构造智能指针
auto_ptr< int > api( new int( 33 ));
2) 拷贝构造
利用已经存在的智能指针来构造新的智能指针
auto_ptr< string > pstr_auto( newstring( "Brontosaurus" ) );
auto_ptr< string > pstr_auto2(pstr_auto );
由于一块动态内存智能由一个智能指针独享,所以在拷贝构造或赋值时都会发生拥有权转移的过程。在此拷贝构造过程中,pstr_auto将失去对字符串内存的全部权。而pstr_auto2将其获得。对象销毁时,pstr_auto2负责内存的自己主动销毁。
3) 赋值
利用已经存在的智能指针来构造新的智能指针
auto_ptr< int > p1( new int( 1024) );
auto_ptr< int > p2( new int( 2048) );
p1 = p2;
在赋值之前,由p1 指向的对象被删除。赋值之后。p1 拥有int 型对象的全部权。该对象值为2048。
p2不再被用来指向该对象。
创建auto_ptr对象时注意的几个问题
(1) auto_ptr的构造函数为explicit。阻止了一般指针隐式类型转换为auto_ptr的构造,所以例如以下的创建方式是编译只是的。
int* p = new int(1); auto_ptr<int> ap = p;
例如以下代码详解了关于explicit的作用。
#include <iostream> using namespace std; class Test1 { public: Test1(int i):iValue(i){}; private: int iValue; char cValue; }; class Test2 { public: explicit Test2(int i):iValue(i){}; private: int iValue; char cValue; }; int main(int argc, char* argv[]) { Test1 t1 = 1;//t1.iValue值为1,cValue值为char类型默认值 Test2 t2 = 2;//编译只是, error: conversion from ‘int‘ to non-scalar type ‘Test2‘ requested }
(2) 因为auto_ptr对象析构时会删除它所拥有的指针。所以使用时避免多个auto_ptr对象管理同一个指针。例如以下的用法应该避免。
int* np = new int(1); auto_ptr<int> p1(np); auto_ptr<int> p2(np);
这样使用会造成p1和p2在析构时都试图删除np,C++标准中多次删除同一个对象会导致没有定义的行为。且当p1析构而p2仍然被使用时,会导致空指针訪问风险。
(3)auto_ptr的内部实现中。析构函数中删除对象使用delete而不是delete[],所以auto_ptr不能用来管理数组指针。
int *p = new int[100]; auto_ptr<int> ap(p);
如上使用auto_ptr的方式,在ap析构时。运行delete,只释放了数组的第一个元素的空间。仍然会造成内存泄漏,全部使用auto_ptr管理数组不合理的。
(4)C++中对一个空指针NULL运行delete操作是安全的。
所以在auto_ptr的析构函数中无须推断它所拥有指针是否为空。
auto_ptr的拷贝构造和赋值
auto_ptr要求对它所拥有的指针全然占有,这一点与引用计数的智能指针不同,也就是说,一个一般指针不能同一时候被两个auto_ptr所拥有,一方面使用者要避免将用同一个指针构造auto_ptr(3.1(2)的那种方式),还有一方面auto_ptr在拷贝构造和赋值运算符重载时要做特殊处理。详细的做法是对全部权进行了全然转移。在拷贝和赋值时,剥夺原auto_ptr对指针的拥有权。赋予当前auto_ptr对指针的拥有权,当前auto_ptr获得auto_ptr的指针。并使原auto_ptr的指针置空。因为会改动原对象,所以auto_ptr的拷贝构造函数以及赋值运算符重重载函数的參数是引用而不是常(const)引用。
这部分须要注意的几个问题
(1) auto_ptr对象被拷贝或者被赋值后。已经失去了对原指针的全部权。此时,对这个auto_ptr的读取操作是不安全的。例如以下代码是不安全的。
auto_ptr<int> p1(new int(1)); auto_ptr<int> p2(p1); cout << *p1 << endl; //and auto_ptr<int> p3=p1; cout << *p1 << endl;
这样的情况较为隐蔽的情形出如今将auto_ptr作为函数參数按值传递,由于在函数调用过程中在函数的作用域中会产生一个局部的暂时auto_ptr对象来接收传入的 auto_ptr(拷贝构造)。这样,传入的实參auto_ptr的对其指针的全部权转移到了暂时auto_ptr对象上,暂时auto_ptr在函数退出时析构,所以当函数调用结束,原实參所指向的对象已经被删除了。
void func(auto_ptr<int> ap) { cout << *ap << endl; } auto_ptr<int> ap(new int(1)); func(ap); cout << *ap1 << endl;//错误。函数调用结束后,ap1已经不再拥有不论什么对象了
因此要避免使用auto_ptr对象作为函数參数按值传递。按引用传递在调用函数是不会发生全部权转移,可是无法预測函数体内的操作。有可能在函数体内进行了全部权的转移,因此按引用传递auto_ptr作为函数參数也是不安全的。
使用const 引用传递则能够阻止在函数体内对auto_ptr对象的全部权转移。假设不得不使用auto_ptr对象作为函数參数时,尽量使用const引用传递參数。
(2) auto_ptr支持所拥有的指针类型之间的隐式类型转换。
class base{}; class derived: public base{}; //下列代码就能够通过,实现从auto_ptr<derived>到auto_ptr<base>的隐式转换。由于derived*能够转换成base*类型 auto_ptr<base> apbase = auto_ptr<derived>(new derived);
(3) C++的STL容器对于容器元素类型的要求是有值语义,即能够赋值和复制。auto_ptr在赋值和复制时都进行了特殊操作。所以auto_ptr对象不能作为STL容器元素。
空的auto_ptr 须要初始化吗?
通常的指针在定义的时候若不指向不论什么对象,我们用Null给其赋值。
对于智能指针。由于构造函数有默认值0,我们能够直接定义空的auto_ptr例如以下:
auto_ptr< int >p_auto_int;
防止两个auto_ptr对象拥有同一个对象(一块内存)
由于auto_ptr的全部权独有,所以以下的代码会造成混乱。
int* p = new int(0);
auto_ptr<int> ap1(p);
auto_ptr<int> ap2(p);
由于ap1与ap2都觉得指针p是归它管的,在析构时都试图删除p, 两次删除同一个对象的行为在C++标准中是没有定义的。
所以我们必须防止这样使用auto_ptr。
警惕智能指针作为參数!
1)按值传递时,函数调用过程中在函数的作用域中会产生一个局部对象来接收传入的auto_ptr(拷贝构造),这样,传入的实參auto_ptr就失去了其对原对象的全部权。而该对象会在函数退出时被局部auto_ptr删除。例如以下例:
void f(auto_ptr<int> ap)
{cout<<*ap;}
auto_ptr<int> ap1(new int(0));
f(ap1);
cout<<*ap1;//错误,经过f(ap1)函数调用。ap1已经不再拥有不论什么对象了。
2)引用或指针时,不会存在上面的拷贝过程。但我们并不知道在函数中对传入的auto_ptr做了什么。假设其中某些操作使其失去了对对象的全部权,那么这还是可能会导致致命的运行期错误。
结论:const reference是智能指针作为參数传递的底线。
auto_ptr不能初始化为指向非动态内存
原因非常easy,delete 表达式会被应用在不是动态分配的指针上这将导致没有定义的程序行为。
auto_ptr经常使用的成员函数
1) get()
返回auto_ptr指向的那个对象的内存地址。例如以下例:
int* p = new int(33);
cout << "the adress of p:"<< p<< endl;
auto_ptr<int> ap1(p);
cout << "the adress of ap1: "<< &ap1<< endl;
cout << "the adress of the objectwhich ap1 point to: " << ap1.get()<< endl;
输出例如以下:
the adress of p: 00481E00
the adress of ap1: 0012FF68
the adress of the object which ap1 point to: 00481E00
第一行与第三行同样。都是int所在的那块内存的地址。第二行是ap1这个类对象本身所在内存的地址。
2) reset()
又一次设置auto_ptr指向的对象。类似于赋值操作,但赋值操作不同意将一个普通指针指直接赋给auto_ptr,而reset()同意。例如以下例:
auto_ptr< string > pstr_auto( newstring( "Brontosaurus" ) );
pstr_auto.reset( new string( "Long -neck" ) );
在样例中,重置前pstr_auto拥有"Brontosaurus"字符内存的全部权,这块内存首先会被释放。之后pstr_auto再拥有"Long-neck"字符内存的全部权。
注:reset(0)能够释放对象,销毁内存。
3) release()
返回auto_ptr指向的那个对象的内存地址,并释放对这个对象的全部权。
用此函数初始化auto_ptr时能够避免两个auto_ptr对象拥有同一个对象的情况(与get函数相比)。
样例例如以下:
auto_ptr< string > pstr_auto( newstring( "Brontosaurus" ) );
auto_ptr< string > pstr_auto2(pstr_auto.get() ); //这是两个auto_ptr拥有同一个对象
auto_ptr< string > pstr_auto2(pstr_auto.release() ); //release能够首先释放全部权
总结:
由于auto_ptr并非完美无缺的,它的确非常方便,但也有缺陷,在使用时要注意避免。首先,不要将auto_ptr对象作为STL容器的元素。C++标准明白禁止这样做。否则可能会碰到不可预见的结果
auto_ptr的还有一个缺陷是将数组作为auto_ptr的參数: auto_ptr<char> pstr (new char[12] ); //数组;为定义
然后释放资源的时候不知道究竟是利用delete pstr,还是 delete[] pstr;
然后收集了关于auto_ptr的几种注意事项:
1、auto_ptr不能共享全部权。
2、auto_ptr不能指向数组
3、auto_ptr不能作为容器的成员。
4、不能通过赋值操作来初始化auto_ptr
std::auto_ptr<int> p(new int(42)); //OK
std::auto_ptr<int> p = new int(42); //ERROR
这是由于auto_ptr 的构造函数被定义为了explicit
5、不要把auto_ptr放入容器
C++智能指针--auto_ptr指针