对auto_ptr的学习
对auto_ptr的学习
auto_ptr是C++标准库提供的类模板,它可以帮助程序员自动管理用new表达式动态分配的单个对象,不过对用new表达式分配的数组管理没有类似的支持,不能用 auto_ptr存储数组,如果这样做了,结果将是未定义的.
auto_ptr对象被初始化为指向由new表达式创建的动态分配对象.当auto_ptr对象的生命期结束时,动态分配的对象被自动释放.
在使用auto_ptr类模板之前,必须包含下面的头文件:
#include <memory>
auto_ptr对象的定义有下面三种形式:
auto_ptr<type_pointed_to> identifier(ptr_allocated_by_new);
auto_ptr<type_pointed_to> identifier(auto_ptr_of_same_type);
auto_ptr<type_pointed_to> identifier;
type_pointed_to代表由new表达式创建的对象的类型.在最常见的情况下,我们希望把auto_ptr直接初始化为new表达式返回的对象地址.可以这样来做:
auto_ptr<int> pi(new int(1024);
pi被初始化为由new表达式创建的对象的地址,且该对象的初始化值为1024.可以检查auto_ptr所指的对象的值,方式与普通指针相同:
if (*pi != 1024);
new表达式创建的对象由pi指向,当pi的生命期结束时,它将被自动释放.如果pi是全局对象,则pi所指向的对象在程序结束时释放.
如果我们用一个class类型的对象初始化auto_ptr对象,比如标准的string类型,则:
auto_ptr<string> pstr_auto(new string("Hello world"));
假设现在希望访问一个字符串操作,对于普通的string指针,会按照下面这样来做:
string* pstr_type = new string("Hello World");
pstr_type->size();
那么,用auto_ptr也是一样的方式:
auto_ptr<string> pstr_auto(new string("Hello world"));
pstr_auto->size();
auto_ptr类模板背后的主要动机是支持与普通指针类型相同的语法,但是为auto_ptr对象所指对象的释放提供自动管理.同时,使用auto_ptr对象并不比直接使用指 针代价更高.
在下面的情况中,用pstr_auto的值初始化pstr_auto2,并且pstr_auto的底层对象是string,会怎样呢?
auto_ptr<string> pstr_auto2(pstr_auto);
假定直接用一个string指针初始化另外一个,比如:
string* pstr_type2(pstr_type);
那么,这两个指针都持有程序空闲存储区内的字符串地址,我们必须小心地将delete表达式只应用在一个指针上,而auto_ptr类模拟支持所有权概念.
当定义auto_ptr时,它知道自己对初始化字符串拥有所有权,并且有责任删除该字符串,这是所有权授予auto_ptr对象的责任.
问题是,当pstr_auto2被初始化为指向与pstr_auto相同的对象时,所有权会发生什么样的变化?我们不希望让两个auto_ptr对象都拥有同一个底层对象--这会引起重复删除对象的问题,这也是我们使用auto_ptr类型首先要防止的.
当一个auto_ptr对象被用另一个auto_ptr对象初始化或赋值时,左边被赋值或初始化的对象就拥有了空闲存储区内底层对象的所有权,而右边的auto_ptr对象则撤消所有责任.于是,在我们的例子中,将是用pstr_auto2删除字符串对象,而不是pstr_auto,pstr_auto不再被用来指向字符串对象.
类似的行为也发生在赋值操作符上,已知下列两个auto_ptr对象.
auto_ptr<int> p1(new int(1024));
auto_ptr<int> p2(new int(2048));
赋值操作符可以将一个auto_ptr对象拷贝到另一个中,如下所示:
p1 = p2;
在赋值之前,由p1指向的对象被删除.赋值之后,p1拥有int型对象的所有权,该对象值为2048,p2不再被用来指向该对象.
在auto_ptr定义的第三种形式中,我们创建一个auto_ptr对象,但是没有用指针将其初始化.例如:
auto_ptr<int> p_auto_int;
因为p_auto_int没有被初始化指向一个对象,所以它的内部指针值被设置为0,这意味着对它解引用会使程序出现未定义的行为,就好像我们直接解引用一个值为0的指针时所发生的一样.
对于普通指针,我们只需测试是否为0,例如:
if (pi != 0);
但是怎样测试一个auto_ptr对象是否指向一个底层对象呢?操作get()返回auto_ptr对象内部的底层指针,所以为了判断auto_ptr指针是否指向一个对象,可以如下编程:
int *pi = 0;
if (p_auto_int.get() != 0 && *p_auto_int != 1024)
*p_auto_int = 1024;
如果它没有指向一个对象,那么怎样使其指向一个呢?即怎样设置一个auto_ptr对象的底层指针?我们可以用reset()操作,例如:
p_auto_int.reset(new int(1024));
我们不能够在auto_ptr对象被定义之后,再用new表达式创建对象的地址来直接向其赋值,因此,我们不能这样写:
void example()
{
auto_ptr<int> pi;
{
pi = new int(4);
}
}
如果为了重置一个auto_ptr对象,我们必须使用reset()函数,我们可以向reset()传递一个指针,如果不希望设置该auto_ptr对象的话,可以传递一个0值.如果auto_ptr当前指向一个对象并且该auto_ptr对象拥有该对象的所有权,则该对象在底层指针被重置之前,首先被删除,例如:
auto_ptr<string> pstr_auto(new string("Hello World"));
pstr_auto.reset(new string("YES")); //在重置之前删除对象Hello World
在这种情况下,用字符串操作assign()对原有的字符串对象重新赋值,比删除原有的字符串对象并重新分配第二个字符串对象更为有效:
//这种情况下,重置的更有效
pstr_auto->assign("YES");
程序设计的一个难事是:有时候仅仅得到正确的结果是不够的,有时候,我们不仅需要正确的结果,而且还需要一个可接受的性能,像调用assign()有效释放和重分配一个字符串这样的小细节就是一个很好的例子,它说明在某些情况下,这些小细节会积聚成可怕的性能瓶颈.
auto_ptr类模板为动态分配内存提供了大量的安全性和便利性,但是我们仍需小心,否则就会陷入麻烦.
1.我们必须小心,不能用一个指向"内存不是通过应用new表达式分配的"指针来初始化或赋值auto_ptr.如果这样做了,delete表达式会被应用在不是动态分配的指针上,这将导致未定义的程序行为.
2.我们必须小心,不能让两个auto_ptr对象拥有空间存储区内同一对象的所有权.一种很显然犯这种错误的方法是,用同一个指针初始化或赋值两个auto_ptr对象,另一个途径是通过get()操作,例如:
auto_ptr<string> pstr_auto(new string("HELLO"));
auto_ptr<string> pstr_auto2(pstr_auto.get());
release()操作允许将一个auto_ptr对象的底层对象初始化或赋值给第二个对象,而不会使两个auto_ptr对象同时拥有同一个对象的所有权.release(0不权像get()一样返回底层对象的地址,而且还释放该对象的所有权,前面代码可被改写如下:
auto_ptr<string> pstr_auto2(pstr_auto.release());
此时两个对象仍然指向同一个对象,但是pstr_auto不再拥有拥有权.