Essential C++ 学习笔记 第七章

异常处理

本来觉得这章会教怎么解决常见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(),其执行结果是中断程序。

至于trycatch以及throw如何安排,是非常值得商榷的。文中进行了大段的讨论,这里不做叙述。

另外需要区别的是,C++的每个异常都可以找到对应的throw。不过segmentation faultbus error这类硬件错误并不在C++的范畴之内。

局部资源管理

什么意思呢?就是new申请的内存空间的释放问题。首先看一段代码:

extern Mutex m;
void f()
{
   int *p = new int;
   m.acquire();

   process(p);

   m.release();
   delete p;
}

正常执行的情况下,pm的内存最终都会释放。但是如果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 <memory>。具体使用方法是:

auto_ptr<int> p(new int);

其实相当于newint类型的类实现,同时这个类还对操作符进行重载,使得基础的操作和原始的变量相同,例如:

auto_ptr<string> 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 <exception>

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() <<endl;
}

最后文中给出了what()的一个实现:

#include <sstream>
#include <string>

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*

完结撒花~ 还是收获不少的~

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值