本来觉得这章会教怎么解决常见bug之类的,结果主要讲的是跟踪代码中的异常并输出,是讲如何把程序写得更加健壮。这些代码画风给人感觉就特别像Java
就是throw
这个命令,给出一个例子:
inline void Traingular_iterator::
check_integrity()
{
if (_index>=Triangular::_max_elems)
throw iterator_overflow(_index,Trinagular::_max_elems);
if (_index>=Triangular::_elems.size())
Triangular::gen_elements(_index+1);
};
//iterator_overflow为一个类
class iterator_overflow{
public:
iterator_overflow(int index, int max)
: _index(index), _max(max){}
//...
};
只看这里可能会感觉到有点奇怪,抛出以后呢?实际上抛出相当于给定了catch的输入值。再看下一节
还是先给例子:
bool some_function()
{
//...
catch(iterator_overflow &iof){
iof.what_happened(log_file);
status = false;
}
//...
}
这里的catch
恰好输入值为前面throw
出来的结构体类型。当然catch
肯会不止一个,当出现多个catch
时。如果出现了throw
,程序会根据throw
的类型逐个比对是否符合catch
中的输入。
另外catch
中可以嵌套throw
,而如果想要捕获任何类型的异常,输入部分也可以写成省略号
catch(...)
{
log_message("exception of unknown type");
}
再引入try
的使用,前三节连在一起看就比较清晰了,先给出例子:
bool has_elem(Triangular_iterator first,
Triangular_iterator last, int elem)
{
bool status = true;
try
{
while(first!=last)
{
if(*first == elem)
return status;
++first;
}
}
catch(iterator_overflow &iof)
{
log_message(iof.what_happened());
log_message("check if iterators address same container");
}
status = false;
return status;
}
//前几章定义的重载
inline int Triangular_iterator::
operator*()
{
check_integrity();
return Triangular::_elems[_index];
}
inline void Triangular_iterator::
check_integrity()
{
if (_index>=Triangular::_max_elems)
throw iterator_overflow(_index, Triangular::_max_elems);
//...
}
首先来解释一下发生了什么,has_elem
函数中try
了一段代码,其中的*first
有可能出错。而*
运算符已经被重载,重载的函数中的check_integrity()
会检查程序的正确性。在check_integrity()
这个函数中如果出现了错误,就会throw
一个前一节定义的结构体iterator_overflow
。
程序的执行规则是如果出现异常抛出,首先判断是否位于try
内,如果是,则判断是否具有异常处理能力,即相对应的catch
。如果有,异常会被处理,程序会继续执行。如果异常不在try
之内,则当前函数代码段会直接跳出。
如果程序执行出现了错误,这个throw
真的被抛出。程序会在当前函数check_integrity()
中并非try
,但是很遗憾并不是,那么程序就不会继续向下执行,注意,是只执行了一开始的if,下面没有执行,函数check_integrity()
就直接跳出了。然后在*
操作符这里,相当于收到了一个throw
,只不过来自它调用的函数,但是检索发现这里也没有try
,那么*
运算符中的
return Triangular::_elems[_index];
就不会执行。然后在调回最外层的函数has_elem()
,此时异常处于try
之内,并且恰好有catch
满足要求,则运行catch
中的内容,然后继续向下运行。
如果处于try
中,但是没找到对应的catch
呢?仍然是跳出。如果一直回到main
函数,还是没有找到合适的catch
呢?程序会调用标准库中terminate()
,其执行结果是中断程序。
至于try
和catch
以及throw
如何安排,是非常值得商榷的。文中进行了大段的讨论,这里不做叙述。
另外需要区别的是,C++的每个异常都可以找到对应的throw
。不过segmentation fault
和bus error
这类硬件错误并不在C++的范畴之内。
什么意思呢?就是new
申请的内存空间的释放问题。首先看一段代码:
extern Mutex m;
void f()
{
int *p = new int;
m.acquire();
process(p);
m.release();
delete p;
}
正常执行的情况下,p
和m
的内存最终都会释放。但是如果process
中出现错误,那么可能程序最终就没有释放内存。直观的解决办法就是在对应的catch
中写入释放内存的代码段,但是有更好的解决办法。即所谓的资源管理(resource managemet)手法,实际上即在初始化阶段即进行资源请求(resource acquisition is initialization)。
听起来名字很高端,其实主要是两个技巧。其一是使用构造函数和析构函数初始化以及释放所有变量。例如:
class MutexLock{
public:
MutexLock(Mutex m) : _lock(m)
{ lock.acquire();}
~MutexLock(){lock.acquire();}
private:
Mutex &_lock;
};
此时如果申请MutexLock
类型的对象,无论是否出现异常,析构函数一定会被执行,那么内存一定可以释放。
其二,是借助函数库#include
。具体使用方法是:
auto_ptr p(new int);
其实相当于new
了int
类型的类实现,同时这个类还对操作符进行重载,使得基础的操作和原始的变量相同,例如:
auto_ptr aps(new string("vermeer"));
string *ps = new string("vermeer");
if (( aps->size() == ps->size()) && (*aps==*ps))
//...
这里的*aps
和*ps
可以直接互相赋值。
当我们收到一个异常时,如果我们希望对出现异常的变量进行操作。就需要借助异常类体系(exception class hierarchy),这个类是基于一个抽象类exception
,同时抽象类声明了一个what()
虚函数,会返回一个const char *
,用来表示抛出异常的文字描述。所以每个异常类体系中的派生类,都必须提供what()
的具体实现。例如:
#include
class iterator_overflow: public exception{
public:
iterator_oveflow(int index, int max)
: _index(index), _max(max)
{}
int index() { return _index;}
int max() { return _max; }
const char* what() const;
private:
int _index;
int _max;
};
同时,利用继承类的规则catch
的输入值只需要写虚类的类型,就可以把这个库中所有的错误类型包括进去,而不是每个写一遍:
catch(const exception &ex)
{
cerr << ex.what() <
最后文中给出了what()
的一个实现:
#include
#include
const char*;
iterator_overflow::
what() const
{
ostringstream ex_msg;
static string msg;
ex_msg << ...
//从内存中导出数据并转换为string
msg = ex_msg.str();
//转换为const char*的表达式
return msg.c_str();
}
这里需要强调的是ostringstream class
提供内存内的输出操作,这需要调用sstream
头文件。而.str()
可以将它转换为string
。另外c_str()
可以将string
转换为所需的const char*
。
完结撒花~ 还是收获不少的~