用于大型程序的工具
--异常处理[续3]
九、auto_ptr类[接上]
5、auto_ptr对象的复制和赋值是破坏性操作
auto_ptr和内置指针对待复制和赋值有非常关键的区别。当复制auto_ptr对象或者将它的值赋给其他auto_ptr对象的时候,将基础对象的所有权从原来的auto_ptr对象转给副本,原来的auto_ptr对象重置为未绑定状态。
auto_ptr
strPtr1(new string("HELLO!"));
auto_ptr
strPtr2(strPtr1); cout << *strPtr2 << endl; cout << *strPtr1 << endl; //段错误
复制之后,strPtr1不再指向任何对象。
与其他复制或赋值操作不同,auto_ptr的复制和赋值改变右操作数,因此,赋值的左右操作数必须都是可修改的左值。
6、赋值删除左操作符指向的对象
除了将所有权从右操作数转给左操作数之外,赋值还删除左操作数原来指向的对象---假如两个对象不同。
auto_ptr
strPtr1(new string("HELLO!"));
auto_ptr
strPtr2; strPtr2 = strPtr1; cout << *strPtr2 << endl; cout << *strPtr1 << endl; //段错误
将strPtr2赋值给strPtr1之后:
1)删除了strPtr1指向的对象。
2)将strPtr2置为指向strPtr1所指的对象。
3)strPtr1是未绑定的auto_ptr对象。
【小心地雷】
因为复制和赋值是破坏性操作,所以auto_ptrs不能将 auto_ptr对象存储在标准容器中。标准库的容器类要求在复制或赋值之后两个对象相等,auto_ptr不满足这一要求,如果将strPtr2赋给strPtr1,则在赋值之后strPtr1!= strPtr2,复制也类似。
7、auto_ptr的默认构造函数
auto_ptr
p_auto; //对象是未绑定的,不指向任何对象
默认情况下,auto_ptr的内部指针值置为0。对未绑定的auto_ptr对象解引用,其效果与对未绑定的指针解引用相同―― 程序出错并且没有定义会发生什么:
*p_auto = 1024;
cout << *p_auto << endl; //Segmentation fault (core dumped)
8、测试auto_ptr对象
测试的效果是确定指针是否为0。但是,不能直接测试auto_ptr对象:
if (p_auto) //Error:auto_ptr 类型没有定义到可用作条件的类型的转换
*p_auto = 1024;
必须使用它的get成员:
if (p_auto.get()) //返回包含在 auto_ptr 对象中的基础指针
{
*p_auto = 1024;
cout << *p_auto << endl;
}
else
{
cout << "Unbound any object!" << endl;
}
【小心地雷】
应该只使用get询问auto_ptr对象或者使用返回的指针值,不能用get作为创建其他auto_ptr对象的实参。
使用get成员初始化其他auto_ptr对象违反auto_ptr类设计原则:在任意时刻只有一个auto_ptrs对象保存给定指针,如果两个auto_ptrs对象保存相同的指针,该指针就会被delete两次。
9、reset操作
auto_ptr对象与内置指针的另一个区别是,不能直接将一个地址(或者其他指针)赋给auto_ptr对象:
auto_ptr
p_auto = new int(1024); //Error
相反,必须调用reset函数来改变指针:
auto_ptr
p_auto;
if (p_auto.get())
{
*p_auto = 1024;
}
else
{
p_auto.reset(new int(2048));
}
cout << *p_auto << endl;
要复位auto_ptr对象,可以将0传递给reset函数。
【小心地雷】
调用auto_ptr对象的 reset函数时,在将auto_ptr对象绑定到其他对象之前,会删除auto_ptr对象所指向的对象(如果存在)。但是,正如自身赋值是没有效果的一样,如果调用该 auto_ptr对象已经保存的同一指针的reset函数,也没有效果,不会删除对象。
【警告:auto_ptr的缺陷】
要正确的使用auto_ptr类,必须坚持该类型强加的下列限制:
1.不要使用auto_ptr对象保存指向静态分配对象的指针,否则,当 auto_ptr对象本身被撤销的时候,它将试图删除指向非动态分配对象的指针,导致未定义的行为。
2.永远不要使用两个auto_ptrs对象指向同一对象,导致这个错误的一种明显方式是,使用同一指针来初始化或者reset两个不同的auto_ptr对象。另一种导致这个错误的微妙方式可能是,使用一个auto_ptr对象的get函数的结果来初始化或者reset另一个auto_ptr对象。
3.不要使用auto_ptr对象保存指向动态分配数组的指针。当auto_ptr对象被删除的时候,它只释放一个对象―― 它使用普通 delete操作符,而不用数组的delete[] 操作符。
4.不要将auto_ptr对象存储在容器中。容器要求所保存的类型定义复制和赋值操作符,使它们表现得类似于内置类型的操作符:在复制(或者赋值)之后,两个对象必须具有相同值,auto_ptr类不满足这个要求。
十、异常说明
异常说明指定,如果函数抛出异常,被抛出的异常将是包含在该说明中的一种,或者是从列出的异常中派生的类型。
1、定义异常说明:
异常说明跟在函数形参表之后。一个异常说明在关键字throw之后跟着一个(可能为空的)由圆括号括住的异常类型列表:
void recoup(int) throw (runtime_error);
说明:如果recoup抛出一个异常,该异常将是runtime_error或者是有runtime_error派生的类型的异常。
空说明列表指出函数不抛出任何异常:
void no_problem() throw ();
异常说明是函数接口的一部分,函数定义以及该函数的任意声明必须具有相同的异常说明。
如果一个函数声明没有指定异常说明,则该函数可以抛出任意类型的异常。
2、违反异常说明
只有在运行时才能检测是否违反函数异常说明。
如果函数抛出了没有在异常说明中列出的异常,就调用标准库函数unexpected。默认情况下,unexpected函数调用terminate函数,terminate函数一般会终止程序。
【小心地雷】
在编译的时候,编译器不能也不会试图验证异常说明。
即使对函数代码的偶然阅读指明,它可能抛出异常说明中没有的异常,编译器也不会给出提示:
//不会给出任何错误说明!
void f() throw ()
{
throw exception();
}
相反,编译器会产生代码以便保证:如果抛出了一个违反异常说明的异常,就调用unexpected函数。
3、确定函数不抛出异常
【最佳实践】
异常说明有用的一种重要情况是:如果函数可以保证不会抛出任何异常。
确定函数将不抛出任何异常,对函数的用户和编译器都有所帮助:
1)知道函数不抛出异常会简化编写调用该函数的异常安全的代码的工作,我们可以知道在调用函数时不必担心异常,
2)如果编译器知道不会抛出异常,它就可以执行被可能抛出异常的代码所抑制的优化。
4、异常说明与成员函数
像非成员函数一样,成员函数声明的异常说明符跟在函数形参表之后。
class