18.1 在下列throw语句中异常对象的类型是什么?
(a)range_error r("error");
throw r;
(b) exception *p = &r;
throw *p;
如果将(b)中的throw语句写成了throw p将发生什么情况?
(a)的throw异常对象类型为 range_error
(b)throw语句异常对象类型为exception
如果写成throw p,则抛出的是一个指针,如果指针指向的是一个局部变量,那么是一个错误行为,因为到catch语句时指针指向的对象已经被销毁了。
18.2 当在指定位置发生了异常时将出现什么情况
void exercise(int* b, int* e)
{
vector<int> v(b, e);
int* p = new int[v.size()];
ifstream in("ints");
//此处发生异常
}
p所指向的内存不会被释放,从而出现内存泄漏
18.3 要想让上面的代码在发生异常时能正常工作,有两种解决方案。请描述两种方法并实现他们
-
自己写一个类来实现动态内存的释放
class pointerArr{ public: void pointerArr(int *pt):p(pt){ } ~pointerArr(){ delete []p;} private: int *p; } void exercise(int* b, int* e) { vector<int> v(b, e); PointerArr p = new int[v.size()]; ifstream in("ints"); }
-
使用shared_ptr,让智能指针管理动态分配的内存
void exercise(int* b, int* e)
{
vector<int> v(b, e);
std::shared_ptr<int> p(new int[v.size()], [](int *p){delete p});
ifstream in("ints");
//此处发生异常
}
- 用try…catch…在catch中释放p指向的内存空间
void exercise(int* b, int* e)
{
vector<int> v(b, e);
int* p = new int[v.size()];
ifstream in("ints");
try{
//此处发生异常
}
catch(exception e)
{
delete []p;
}
}
18.4 查看图18.1(P693)所示的继承体系,说明下面的try块有何错误并修改它
try{
//使用C++标准库
}
catch(exception){
//...
}
catch(const runtime_error &re){
//...
}
catch(overflow_error eobj)
{
//....
}
不应该先捕获异常体系中的基类,这样会造成除了catch(excepiton)
会被匹配,其的catch永远不会被捕获。应该把继承连最底端的类放在前面,而将继承链最顶端的类放在后面。应修改为:
try{
//使用C++标准库
}
catch(overflow_error eobj)
{
//....
}
catch(const runtime_error &re){
//...
}
catch(exception){
//...
}
18.5 修改下面的main函数,使其能捕获图18.1(P693)所示的任何异常类型:
int main()
{
//使用C++标准库
}
处理代码应该首先打印异常相关的错误信息,然后调用abort(定义在cstdlib头文件中)终止main函数
#include<iostream>
#include<exception>
#include<cstdlib>
using std::cout;
using std::endl;
int main()
{
try {
//使用C++标准库
}
catch (std::bad_cast& e) {
cout << e.what() << endl;
abort();
}
catch (std::overflow_error& e) {
cout << e.what() << endl;
abort();
}
catch (std::bad_alloc& e) {
cout << e.what() << endl;
abort();
}
catch (std::underflow_error& e) {
cout << e.what() << endl;
abort();
}
catch (std::bad_cast& e) {
cout << e.what() << endl;
abort();
}
catch (std::range_error& e) {
cout << e.what() << endl;
abort();
}
catch (std::runtime_error& e) {
cout << e.what() << endl;
abort();
}
catch (std::domain_error& e) {
cout << e.what() << endl;
abort();
}
catch (std::invalid_argument& e) {
cout << e.what() << endl;
abort();
}
catch (std::out_of_range& e) {
cout << e.what() << endl;
abort();
}
catch (std::length_error& e) {
cout << e.what() << endl;
abort();
}
catch (std::logic_error& e) {
cout << e.what() << endl;
abort();
}
catch (std::exception& e) {
cout << e.what() << endl;
abort();
}
return 0;
}
18.6 已知下面的异常类型和catch语句,书写一个throw表达式,使其创建的异常对象能被这些catch语句捕获
(a) class exceptionType{};
catch(exceptionType *pet){}
(b) catch(...){ }
(c) typedef int EXCEPTYPE;
catch(EXCEPTYPE) {}
答案:
(a) throw new exceptionType();
(b) throw std::runtime_error;
(c) EXCEPTYPE e = 4; throw e;
18.9 定义本节描述的书店程序异常类,然后为Sales_data类重新编写一个符合赋值运算符并令其抛出一个异常。
//处理异常的类
class out_of_stock:public runtime_error {
public:
explicit out_of_stock(const string& s) :
runtime_error(s) {}
};
class isbn_mismatch :public logic_error {
public:
explicit isbn_mismatch(const string& s) :
logic_error(s) {}
isbn_mismatch(const string& s, const string& lhs, const string& rhs) :
logic_error(s), left(lhs), right(rhs) {}
const string left, right;
};
Sales_data& Sales_data::operator+=(const Sales_data& rhs)
{
//如果参与 加法的两个对象不是同一书籍,则抛出一个异常
if (isbn() != rhs.isbn())
throw isbn_mismatch("wrong isbns", isbn(), rhs.isbn());
//复合赋值运算符一般先+,后执行=
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this; //返回左侧运算对象的引用
}
18.10 编写程序令其对两个ISBN号不同的对象执行Sales_data加法运算。为该程序编写两个不同版本,一个处理异常,另一个不处理异常。观察并比较这两个程序的行为,用心体会当出现了一个违背捕获的异常时程序会发生什么情况。
出现了未被捕获的异常时会直接terminate,提示有未被处理的异常,终止程序
18.11 为什么what函数不应该抛出异常
what中如果抛出异常,需要try{}catch(){}捕获,再次调用what,导致一直循环,最终耗尽内存。
18.13 什么时候应该使用未命名的命名空间
当需要在文件中进行声明静态全局变量(即希望在单个文件范围内使用静态变量)时需采用在未命名的命名空间声明变量替代
即希望声明的变量对所在文件外的其他文件不可见,并且在第一次使用前创建,直到程序结束才销毁(静态生命周期)
18.14 假设下面的operator*声明的是嵌套的命名空间mathLib::MatrixLib的一个成员:
namespace mathLib {
namespace MatrixLib {
class matrix {
};
matrix operator*(const matrix&, const matrix&);
}
}
请问你该如何在全局作用域中声明该运算符?
using mathLib::MatrixLib::operator*(const matrix&, const matrix&);
18.15 说明using指示与using声明的区别
using声明可以一次只引入命名空间的一个成员,它的有效范围从using声明的地方开始,一直到using声明所在的作用域结束为止,using std::endl;
using指示与using声明不同在于,无法控制哪些名字是可见的,因为所有名字都是可见的.using指示以关键字using开始,后面是关键字namespace以及命名空间的名字。using指示会把所有名字注入到全局作用域(如using namespace std;)
18.16 假定下面代码中标记为“位置1”的地方是对于命名空间Exercise中所有成员的using声明,请解释代码的含义。如果这些using声明出现在“位置2”又会怎样呢?将using声明变为using指示,重新回答之前的问题。
namespace Exercise {
int ivar = 0;
double dvar = 0;
const int limit = 1000;
}
int ivar = 0;
//位置1
void manip()
{
//位置2
double dvar = 3.1416;
int iobj = limit + 1;
++ivar;
++::ivar;
}
-
在位置1进行using声明:
namespace Exercise { int ivar = 0; double dvar = 0; const int limit = 1000; } int ivar = 0; using Exercise::ivar; using Exercise::dvar; using Exercise::limit;//using声明导致多次声明 void manip() { //位置2 double dvar = 3.1416; //隐藏Exercise::dvar int iobj = limit + 1; //使用Exercise::limit ++ivar; ++::ivar; }
-
在位置2进行using声明
void manip() { //位置2 using Exercise::ivar; using Exercise::dvar; using Exercise::limit; double dvar = 3.1416; //错误重定义,多次初始化 int iobj = limit + 1; //Exercise::limit ++ivar; //使用Exercise::ivar ++::ivar; //使用全局的ivar }
-
在位置1进行using指示
namespace Exercise { int ivar = 0; double dvar = 0; const int limit = 1000; } int ivar = 0; //位置1 using namespace Exercise; void manip() { //位置2 double dvar = 3.1416; int iobj = limit + 1; //Exercise::limit ++ivar; //不明确,不知道是使用::ivar还是Exercise::ivar ++::ivar; //使用全局的ivar }
-
在位置2进行using指示
void manip() { //位置2 using namespace Exercise; //会把Exercise中名字注入全局域 double dvar = 3.1416; int iobj = limit + 1; //Exercise::limit ++ivar; //不明确,不知道是使用::ivar还是Exercise::ivar ++::ivar; //使用全局的ivar }
18.18 已知有下面的swap典型定义(见13.3节,457页)当mem1时一个string时程序使用swap的哪个版本?如果mem1是int呢?说明这两种情况下名字查找过程。
void swap(T v1, T v2)
{
using std::swap;
swap(v1.mem1, v2.mem1)
}
当mem1是string时,会先在string类中查找swap函数,找到则不用std版本的。若为int类型,则直接使用标准库版本的swap(通过using声明已注入std::swap函数声明)
18.19 如果对swap的调用形如std::swap(v1.mem1, v2.mem2)
将发生什么?
只能使用标准库版本的swap。
18.20 在下面代码中,确定哪个函数与compute调用匹配。列出所有候选函数和可行函数,对于每个可行函数的实参与形参匹配过程来说发生了哪种类型转换?
namespace primerLib {
void compute();
void compute(const void*);
}
//using声明
using primerLib::compute;
void compute(int) {}
void compute(double, double = 3.4) {}
void compute(char*, char* = 0) {}
void f()
{
compute(0);
}
如果将using声明置于f函数中compute的调用点之前将发生什么情况?请重新回答之前那些问题。
答:候选函数有:
void compute(int) {}
void compute(double, double = 3.4) {}
void compute(char*, char* = 0) {}
void compute();
void compute(const void*);
可行函数有:
void compute(int) {} // 1
void compute(double, double = 3.4) {} //2
void compute(char*, char* = 0) {} //3
void compute(const void*); //4
对于1来说没有任何类型转换,最佳匹配
对于2来说,发生了int->double的转换
对于3来说,发生了int->char*转换
对于4来说,发生了int->const void *转换
若将using声明置于f函数中compute的调用点之前,则把名字compute注入using声明语句所在的作用域中,覆盖了全局的compute函数,最佳匹配compute(const void *)
18.21 解释下列声明的含义,在它们当中存在错误吗?如果有,请指出来并说明错误的原因。
(a)class CADVehicle : public CAD,Vehicle{};
(b)class DblList:public List,public List{};
(c)class iostream: public istream, public ostream{};
(a)没错,CADVehicle public继承CAD类,private继承Vehicle类。但是忽略了Vehicle前的访问说明符,那么默认是private
(b)错误,重复继承List类
©没有错误,iostream类public继承istream类,public继承ostream类
18.22 已知存在如下所示的继承体系,其中每个类都定义了一个默认构造函数
class A{};
class B:public A{};
class C:public B{};
class X{};
class Y{};
class Z : public X, public Y{};
class MI : public C, public Z{};
对于下面的定义来说,构造函数执行顺序时怎样的?
MI mi;
顺序是:A-B-C-X-Y-Z-MI
18.23 使用练习18.22的继承体系以及下面定义的类D,同时假定每个类都定义了默认构造函数,请问下面的哪些类型转换是不被允许的?
class D :public X, public C {};
void p18_23()
{
D* pd = new D;
X* px = pd;
A* pa = pd;//A是D的间接基类
B* pb = pd;//B为D的间接基类
C* pc = pd;
}
都是允许的
18.24 在第714页,我们用一个指向Panda对象的Bear指针进行了一系列调用,假设我们使用的是一个指向Panda对象的ZooAnimal指针将发生什么情况。请对这些调用语句逐一进行说明
ZooAnimal *pz = new Panda("ying_yang");
pz->print(); //正确 Panda::print()
pz->cuddle(); //错误,不属于ZooAnimal的接口
pz->highlight(); //错误不属于ZooAnimal的接口
delete pz; //正确,Panda::~Panda
18.25 假设我们有两个基类Base1和Base2,它们各自定义了一个名为print的虚成员和一个虚析构函数。从这两个基类中我们派生出下面的类,它们都重新定义了print函数:
class Base1 {
public:
Base1() = default;
virtual void print()
{
cout << "Base1-print()" << endl;
}
virtual ~Base1()
{
cout << "Base1-dtor" << endl;
}
};
class Base2 {
public:
Base2() = default;
virtual void print()
{
cout << "Base2-print()" << endl;
}
virtual ~Base2()
{
cout << "Base2-dtor" << endl;
}
};
class D1:public Base1 {
public:
D1() = default;
void print() override
{
cout << "D1-print()" << endl;
}
};
class D2 :public Base1 {
public:
D2() = default;
void print() override
{
cout << "D2-print()" << endl;
}
};
class MI :public D1, public D2 {
public:
MI() = default;
void print() override
{
cout << "MI-print()" << endl;
}
};
通过下面的指针,指出在每个调用中分别使用了哪个函数:
void p18_25()
{
Base1* pb1 = new MI;
Base2* pb2 = new MI;
D1* pd1 = new MI;
D2* pd2 = new MI;
pb1->print(); // a MI::print()
pd1->print(); //b MI::print()
pd2->print(); //c MI::print()
delete pb2; //d MI::~MI()
delete pd1; //e MI::~MI()
delete pd2; //f; MI::~MI()
}
18.26 已知如下所示的继承体系
struct Base1 {
void print(int) const; // 默认情况下访问权限是public
protected:
int ival;
double dval;
char cval;
private:
int* id;
};
struct Base2 {
void print(double) const;
protected:
double fval;
private:
double dval;
};
struct Derived :public Base1 {
void print(string) const;
protected:
string sval;
double dval;
};
struct MI :public Derived, public Base2 {
void print(vector<double>);
protected:
int* ival;
vector<double> dvec;
};
下面对print的调用为什么是错误的?适当修改MI,令其对print的调用可以编译通过并正确执行
MI mi;
mi.print(42);
答:因为在MI的作用域中只找到void print(std::vec<double>)
函数,因此参数类型不匹配。MI中增加函数
struct MI :public Derived, public Base2 {
void print(vector<double>);
void print(int i) const
{
Base1::print(i);
}
protected:
int* ival;
vector<double> dvec;
};
18.27 已知如上所示的继承体系,同时假定为MI添加了一个名为foo的函数:
int ival;
double dval;
void MI::foo(double cval)
{
int dval;
//练习中的问题发生在此处
}
(a)列出在MI::foo中可见的所有名字
(b)是否存在某个可见的名字是继承自多个基类的
©将Base1的dval成员与Derived的dval成员求和后赋值给dval的局部实例
(d)将MI::dvec的最后一个元素赋值给Base2::fval
(e)将从Base1继承的cval赋给Derived继承的sval的第一个字符。
答:
(a)::ival, ::dval,
(MI中) dval, print, ival, dvec,
Derived::sval, Derived::dval,Derived::print
Base2::print,Base2::fval,
Base1::print, Base1::ival, Base1::dval, Base1::cval
(b)存在,(在多个基类中有同名成员)ival, dval, print
©
dval = Base1::dval + Derived::dval;
(d)
Base2::fval = MI::dvec.back();
(e)
Derived::sval[0] = Base1::cval;
18.28 已知存在如下的继承体系,在VMI类内哪些继承而来的成员无须前缀限定符就能直接访问?哪些必须有限定符才能访问?说明你的原因。
struct Base {
void bar(int);
protected:
int ival;
};
struct Derived1 :virtual public Base {
void bar(char);
void foo(char);
protected:
char cval;
};
struct Derived2 :virtual public Base {
void foo(int);
protected:
int ival;
char cval;
};
class VMI :public Derived1, public Derived2
{};
无须前缀限定符可访问的成员:Derived1::bar(char)覆盖了Base::bar(int) Derived2::ival 隐藏了 Base::ival
需要限定符访问的成员: Derived1::cval, Derived2::cval, 因为二义性
Base::bar(int), Base::ival 被Derived1或Derived2同名成员隐藏了
Derived1::foo(char), Derived2::foo(int)因为二义性
18.29 已知有如下所示的继承关系
class Class {};
class Base:public Class{};
class D1:virtual public Base{};
class D2 :virtual public Base {};
class MI:public D1, public D2{};
class Final :public MI, public Class {};
(a) 当作用于一个Final对象时,构造函数和析构函数的执行次序分别是什么?
(b) 在一个Final对象中有几个Base部分?几个Class部分?
©下面哪些赋值运算将造成编译错误
Base* pb; Class* pc; MI* pmi; D2* pd2;
pb = new Class;//a
pc = new Final; //b
pmi = pb; //c
pd2 = pmi; //d
答:(a)构造函数执行次序为:Class、Base、D1、D2、MI、Class、Final
析构函数与构造函数执行次序相反
(b) 有一个Base部分,两个Class部分
© a、c错误,不能将父类指针转换为子类指针