《C++ Primer》第18章 18.1节习题答案

《C++ Primer》第18章 用于大型程序的工具

本章介绍了一些用于大型程序开发的语言特性,包括:

●异常处理。

●命名空间。

●多重继承与虚继承。

本章的练习着重帮助读者理解异常的基本概念,练习异常捕获和异常处理的设计;理解命名空间和名字解析;理解多重继承和虚继承的概念,并进行相应的设计练习。

18.1节习题答案

练习18.1:在下列throw语句中异常对象的类型是什么?

(a) range_error r("error");			(b) exception *p = &r;
	throw r;							throw *p;

如果将(b)中的throw语句写成了throw p将发生什么情况?

【出题思路】

理解异常对象的类型的匹配方法和规则。

【解答】

(a)异常对象r的类型是range_error。

(b)被抛出的异常对象是对指针p解引用的结果,其类型与p的静态类型相匹配,为exception。若写成throw p,则抛出的异常对象是exception*类型。读者可尝试编译、运行这几条语句,观察系统的提示。

练习18.2:当在指定的位置发生了异常时将出现什么情况?

void exercise(int *b, int *e)
{
	vector<int> v(b,e);
	int *p = new int[v.size()];
	ifstream in("ints");
	//此处发生异常
}

【出题思路】

深入理解异常发生时可能对程序已运行部分产生的影响。

【解答】

在new操作后发生的异常使得动态分配的数组没有被撤销,从而造成内存泄漏。

练习18.3:要想让上面的代码在发生异常时能正常工作,有两种解决方案。请描述这两种方法并实现它们。

【出题思路】

在理解异常发生可能造成的后果之后,尝试使用try-catch方法和封装策略正确处理异常。

【解答】

方法一:将有可能发生异常的代码放在try块中,以便在异常发生时捕获异常。

void exercise(int *b, int *e)
{
	vector<int> v(b, e);
	int *p = new int[v.size()];
	try{
		ifstream in("ints");
		//此处发生异常
	}
	catch {
		delete p; //释放数组
		//进行其他处理
	}
	// ...
}

方法二:定义一个类来封装数组的分配和释放,以保证正确释放资源:

class Resource {
public:
	Resource(size_t sz): r(new int[sz]) {  }
	~Resource() { if(r) delete r; }
private:
	int *r;
}

函数exercise相应修改为:

void exercise(int *b, int *e)
{
	vector<int> v(b, e);
	int *p = new int[v.size()];
	Resource res(v.size());
	ifstream in("ints");
	//此处发生异常
	// ...
}

注意,此处给出的Resource类非常简略,实际应用时,还需定义其他操作,包括复制构造函数、复制操作、解引用操作、箭头操作、下标操作等,以支持内置指针及数组的使用方式并保证自动删除Resource对象所引用的数组。另外,可将该Resource类定义为类模板,以支持多种数组元素类型。

练习18.4:查看图18.1(第693页)所示的继承体系,说明下面的try块有何错误并修改它。

try {
	//使用C++标准库
} catch(exception) {
	// ...
} catch(const runtime_error &re) {
	// ...
} catch(overflow_error eobj) { /* ... */ }

【出题思路】

理解异常处理中catch语句的匹配顺序。

【解答】

该try块中使用的exception、runtime_error及overflow_error是标准库中定义的异常类。它们是因继承而相关的:runtime_error类继承exception类,overflow_error类继承runtime_error类。在使用来自继承层次的异常时,catch子句应该从最低派生类型到最高派生类型排序,以便派生类型的处理代码出现在其基类类型的catch之前,所以上述块中,catch子句的顺序错误。

可修改为:

try {
	//使用C++标准库
} catch(overflow_error eobj) {
	// ...
} catch(const runtime_error &re) {
	// ...
} catch(exception) { /* ... */ }

练习18.5:修改下面的main函数,使其能捕获图18.1(第693页)所示的任何异常类型:

int main(){
//使用C++标准库
}

处理代码应该首先打印异常相关的错误信息,然后调用abort(定义在cstdlib头文件中)终止main函数。

【出题思路】

本题练习捕获并打印所有类型的异常。

【解答】

int main(){
	try {
		//使用C++标准库
	} catch(const exception &e) {
		cerr << e.what() << endl;
		abort();
	} 
	return 0;
}

练习18.6:已知下面的异常类型和catch语句,书写一个throw表达式使其创建的异常对象能被这些catch语句捕获:

(a)class exceptionType {  };
   catch(exceptionType *pet) {  }
(b)catch(...) {  }
(c)typedef int EXCPTYPE;
   catch(EXCPTYPE) {   }

【出题思路】

理解使用throw时异常对象的类型一般需要与catch语句捕获的对象类型对应。

【解答】

(a)throw new exceptionType();	//动态创建一个异常对象并抛出其指针
(b)throw 8;						//被抛出的表达式为任意类型
(c)throw 11;					//被抛出的表达式为int类型即可

练习18.7:根据第16章的介绍定义你自己的Blob和BlobPtr,注意将构造函数写成函数try语句块。【出题思路】

本题练习在实际应用中处理构造函数异常的常用方法:使用try语句块。

【解答】

template <typename T> class Blob {
public:
	typedef T value_type;
	typedef typename std::vector<T>::size_type size_type;
	//构造函数
	Blob();
	Blob(std::initializer_list<T> il) try:
		data(std::make_shared<std::vector<T>>(il)) {
			/* 空函数体*/
	} catch(const std::bad_alloc &e) { handle_out_of_memory(e); }
	// Blob中的元素数目
	size_type size() const { return data->size(); }
	bool empty() const { return data->empty(); }
	//添加和删除元素
	void push_back(const T &t) {data->push_back(t); }
	//移动版本,参见13.6.3节(第548页)
	void push_back(T &&t) { data->push_back(std::move(t)); }
	void pop_back();
	//元素访问
	T& back();
	T& operator[](size_type i); //在14.5节中定义
private:
	std::shared_ptr<std::vector<T>> data;
	//若data[i]无效,则抛出msg
	void check(size_type i, const std::string &msg) const;
};

template <typename T> class BlobPtr {
public:
	BlobPtr(): curr(0) { }
	BlobPtr(Blob<T> &a, size_t sz = 0) try:
		wptr(a.data), curr(sz) {  }
	} catch(const std::bad_alloc &e) { handle_out_of_memory(e); }
	T& operator*() const
	{
		auto p = check(curr, "dereference past end");
		return (*p)[curr];//(*p)为本对象指向的vector
	}
	//递增和递减
	BlobPtr& operator++(); //前置运算符
	BlobPtr& operator--();
private:
	//若检查成功,check返回一个指向vector的shared_ptr
	std::shared_ptr<std::vector<T>>
		check(std::size_t, const std::string &) const;
	//保存一个weak_ptr,表示底层vector可能被销毁
	std::weak_ptr<std::vector<T>> wptr;
	std::size_t curr;//数组中的当前位置
}

练习18.8:回顾你之前编写的各个类,为它们的构造函数和析构函数添加正确的异常说明。如果你认为某个析构函数可能抛出异常,尝试修改代码使得该析构函数不会抛出异常。

【出题思路】

练习18.7中已经说明了构造函数的异常处理方法,练习18.8中着重理解析构函数的异常说明。

【解答】

class MyTest_Base
{
public:
	virtual ~MyTest_Base()
	{
		cout << "开始准备销毁一个MyTest_Base类型的对象" << endl;
		//把异常完全封装在构构函数内部
		try {
			//注意:在析构函数中抛出了异常
			throw std::exception("在析构函数中故意抛出一个异常,测试!");
		}
		catch(...) {  }
	}
	
	void Func() throw()
	{
		throw std::exception("故意抛出一个异常,测试!");
	}	
	void Other() {  }	
}//重点是把可能发生异常的部分用try封装起来	

练习18.9:定义本节描述的书店程序异常类,然后为Sales_data类重新编写一个复合赋值运算符并令其抛出一个异常。

【出题思路】

本题练习书店应用程序中复合赋值运算符的编写和异常判断。

【解答】

Sales_data& Sales_data::operator-=(const Sales_data &rhs)
{
	if(isbn() != rhs.isbn())
		throw isbn_mismatch("wrong isbns", isbn(), rhs.isbn());
	units_sold -= rhs.unist_sold;
	revenue -= rhs.revenue;
	return *this;
}

练习18.10:编写程序令其对两个ISBN编号不相同的对象执行Sales_data的加法运算。为该程序编写两个不同的版本:一个处理异常、另一个不处理异常。观察并比较这两个程序的行为,用心体会当出现了一个未被捕获的异常时程序会发生什么情况。

【出题思路】

体会处理或不处理异常对程序的影响。下面是处理异常和不处理异常的主要程序片段。

【解答】


//处理异常
Sales_data item1, item2, sum;
while(cin >> item1 >> item2) { //读取两条交易信息
	try {
		sum = item1 + item2;	//计算它们的和
		return sum;
	}catch(const isbn_mismatch &e) {
		cerr << e.what() << ": left isbn(" << e.left
				<< ") right isbn(" << e.right << ")" << endl;
	}
}
//不处理异常
Sales_data item1, item2, sum;
	while(cin >> item1 >> item2) {//读取两条交易信息
		sum = item1 + item2;		//计算它们的和
		return sum;
	}

练习18.11:为什么what函数不应该抛出异常?

【出题思路】

深入理解what函数的作用和在异常处理中所处的重要位置。

【解答】

what函数是在catch异常后用于提取异常基本信息的虚函数,what函数是确保不会抛出任何异常的。如果what函数抛出了异常,则会在新产生的异常中由于what函数继续产生异常,将会产生抛出异常的死循环。所以what函数必须确保不抛出异常

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值