第17章 用于大型程序的工具
【127】17.1下面的throw语句中,异常对象的类型是什么?
a)range_errorr(“error”); throw r;
b)exception*p=&r; throw *p;
答:a)异常对象r的类型是range_error
b)被抛出的异常对象是对指针p解引用的结果,其类型与p的静态类型相匹配,为exception。
【128】17.2如果上题中第二个throw语句写成throwp,会发生什么情况?
答:如果r是一个局部对象,则throw p抛出的p是指向局部对象的指针,那么,在执行对应异常处理代码时,有可能对象r已不存在,从而导致程序不能正确运行。所以,通常throw语句不应该抛出指针,尤其不应该抛出指向局部对象的指针。
【129】17.3解释下面try块为什么不正确。
try{
…
}catch(exception){
…
}catch(construntime_error &re){
…
}catch(overflow_erroreobj){…}
答:该try块中使用的exception、runtime_error及overflow_error是标准库中定义的异常类,它们是因继承而相关的:runtime_error类继承exception类,overflow_error类继承runtime_error类,在使用来自继承层次的异常时,catch子句应该从最低派生类型到最高派生类型排序,以便派生类型的处理代码出现在基类类型的catch之前,所以,上述块中catch子句的顺序错误。
【130】17.5对于下面的异常类型以及catch子句,编写一个throw表达式,该表达式创建一个可被每个catch子句捕获的异常对象。
a)classexceptionType{};
catch(exceprionType *pet){}
b)catch(…){};
c)enummathErr{overflow,underflow,zeroDivide};
catch(mathErr &ref){}
d)typedefint EXCPTYPE;
catch(EXCPTYPE){}
答:a)throw new exceptionType(); //
b)throw8; //
c)throwoverflow; //
d)throw10; //
【131】17.6给定下面的函数,解释当发生异常时会发生什么?
voidexercise(int *b,int *e)
{
vector<int>v(b,e);
int*p=new int(v.size());
ifstreamin(“ints”);
//exceptionoccurs here
}
答:在new操作之后发生的异常使得动态分配的数组没有被撤销。
【132】17.7有两种方法可以使上面的代码是异常安全的,描述并实现它们。
答:一种方法是将有可能发生异常的代码放在try块中,以便在异常发生时捕获异常:
voidexercise(int *b,int *e)
{
vector<int>v(b,e);
int*p=new int(v.size());
try{
ifstreamin(“ints”);
//exceptionoccurs here
}
catch{
deletep;
}
}
另一种方法是定义一个类来封装数组的分配和释放,以保证正确释放资源:
classResource{
public:
Resource(size_tsz):r(new int[sz]){}
~Resource(){if(r) delete r;}
private:
int*r;
};
函数exercise相应修改为:
voidexercise(int *b,int *e)
{
vector<int>v(b,e);
Resourceres(v.size());
ifstreamin(“ints”);
//exceptionoccurs here
}
【133】17.8下面的auto_ptr声明中,哪些是不合法的或者可能导致随后的程序错误?解释每个声明的问题。
intix=1024,*pi=&ix,*pi2=new int(2048);
typedefauto_ptr<int> IntP;
a)IntP p0(ix);
不合法,必须向auto_ptr的构造函数传递一个由new操作返回的指针,ix不是指针;
b)IntP p1(pi);
可能导致随后的程序错误:auto_ptr只能用于管理从new操作返回的一个对象,p1是指向ix的指针,而ix是静态分配的对象;
c)IntP p2(pi2);
正确;
d)IntP p3(&ix);
可能导致随后的程序错误:auto_ptr只能用于管理从new操作返回的一个对象,此处传给auto_ptr构造函数的是静态分配的对象ix的地址;
e)IntP p4(new int(2048));
正确;
f)IntP p5(p2.get());
可能导致随后的程序错误:因为两个auto_ptr对象p2和p5拥有同一基础对象(保存相同的指针),会导致同一指针被delete两次。
【134】17.10如果函数有形如throw()的异常说明,它能抛出什么异常?如果没有异常说明呢?
答:如果函数有形如throw()的异常说明,则该函数不抛出任何异常
如果函数没有异常说明,则该函数可以抛出任意类型的异常
【135】17.11如果有,下面哪个初始化是错误的,为什么?
voidexample() throw(string);
a)void(*pf1)()=example;
b)void(*pf2)() throw() example;
答:b)是错误的,因为用另一个指针初始化带有异常说明的函数指针时,源指针的异常说明必须至少与目标指针一样严格。函数指针pf2的声明指出,pf2指向不抛出任何异常的函数,而example函数的声明指出它能抛出string类型的异常,example函数抛出的异常类型超过了pf2所指定的,所以,对pf2而言,example函数不是有效的初始化式,会引发编译时错误。
【136】17.12下面的函数可以抛出哪些异常?
a)voidoperator[] throw(logic_error);
b)intop(int) throw(underflow_error,overflow error);
c)charmanip(string) throw();
d)voidprocess();
答:异常说明指定,如果函数抛出异常,被抛出的异常将是包含在该说明中的一种,或者是从列出的异常类型中派生的类型,因此:
a)可以抛出logic_error、domain_error等等异常;
b)可以抛出underflow_error和overflow_error类型的异常;
c)不抛出任何异常
d)可以抛出任意类型的异常
【137】17.18何时可以使用未命名的命名空间?
答:通常,当需要声明局部于文件的实体时,可以使用未命名的命名空间(在文件的最外层作用域中定义未命名的命名空间。
【138】17.25给定下面的类层次,其中每个类定义了一个默认构造函数:
classX{…};
classA{…};
classB:public A{…};
classC:private B{…};
classD:public X,public C{…};
如果有,下面转换中哪些是不允许的?
D*pd =new D;
a)X*px=pd;
b)A*pa=pd;
c)B*pb=pd;
d)C*pc=pd;
答:c)和b)是不允许的。
因为C对B的继承是私有继承,使得在D中B的默认构造函数成为不可访问的,所以尽管存在从”D”到”B”以及从”D”到”A”的转换,但这些转换不可访问。
【139】17.34有一种情况下派生类不必为虚基类提供初始化式,这种情况是什么?
答:派生类不必为虚基类提供初始化式的情况是:虚基类具有默认构造函数;