C++异常处理学习笔记 -- 持续更新

笔者在看《C++标准程序库》,看到“错误(Error)处理和异常(Exception)处理”这一章节,关于这方面的内容,笔者经常使用的《C++ Primer》里写的不是很详细,《C++标准程序库》也一样,根据笔者理解和测试,以此文做个总结吧!
C++标准库由不同的成分构成。来源不同(不是由某个人或某个组织以某种统一的形式弄成的),设计与实现风格迥异。而错误处理和异常处理正是这种差异的一个典型代表。标准程序库中有一部分,例如string classes,支持具体的错误处理,它们检查所有可能发生的错误,并于错误发生时抛出异常。至于其他的部分如STL和valarrays,效率重于安全,因此几乎不检测逻辑错误,并且只在执行期(runtime)发生错误时才抛出异常。

P.S:一句话,STL的设计,效率重于安全,要想用好STL,必须要对异常方面的知识非常了解。

有哪些异常?
语言本身或标准程序库所抛出的所有异常都派生自exception,主要包括三类:
a. C++语言本身所支持的异常(也即不管是string还是STL,只要是C++代码都可能会出现的异常);
     bad_alloc、bad_cast、bad_typeid、bad_exception

b. C++标准库所所发出的异常
     ios_base::failure、logic_error、domain_error、invalid_argument、length_error、out_of_range
     P.S:除了ios_base::failure和logic_error是平级之外,logic_error是其他几个异常的基类;

c. 程序作用域之外发出的异常
     runtime_exception、range_error、overflow_error、underflow_error
     P.S:这类异常表示“不在程序范围内,且不容易回避”的事件。笔者此时也是一知半解,但它们不是本文重点。

对于以上这么多种类的异常,具体的意思笔者这里就略过了,许多书籍上都有描述,并且很容易找到“标准异常阶层体系图”参考。

异常是什么?书上都说它是语言处理错误的某种机制之类,但对于写“hello world”级别代码的我们,它有什么直观意义?

举个例子吧!

假设有一个getData()函数,它的作用是返回某种内置数据类型,它可能是以下某种定义方式:
a. char getData(...) { ... }     // char型返回值
b. int * getData( ... ) { ... }     // int *型返回值
c. int getData( ... ) { ... }     // int型返回值

这个函数可能是通过计算进而得到某个结果进行返回,可能是从某个地方获取一个值把它返回,前者可能会发生计算错误,后者可能发生取值错误(这个可能性就比较高了),比如指定地址的值不存在等等。无论如何,函数需要把操作结果告诉用户(函数的使用者),即用户需要通过某种途径知道此函数是否成功执行了 。

若返回值是char类型,可以返回‘\0’来表示“失败”(其实也不见得是个好的方式),因为有效字符不会是‘\0’值;
若返回值是指针类型,可以返回NULL来表示“失败”,因为有效指针不会是NULL;

但若返回类型是int类型呢?该返回什么值呢?事实上,对于int类型来说,没有任何值是无效的。但总要解决这个问题啊!

这时异常就可以解决这个问题。 对于带有返回值的函数,结束函数的运行,或者通过return语句结束函数,或者通过以抛出异常结束函数 。 

所以,上述int型返回值的getData函数可以如下这么写:
int getData()
{
     bool state = false;
     int result = 0;     // as a return value
     // do something
     state = do_something(...);
     if (!state)
     {
          throw someExceptionObject;     // someExceptionObject表示某个异常对象
     }
     return result;
}

然后在调用这个getData()的程序段里可以这么处理
{
     try
     {
          int result = getData();
     }
     catch (someExceptionClass & e)     // someException表示某个异常类
     {
          // error;
     }
}

这里插一个概念 -- 用户:比如在A函数中调用B函数,对于B函数来说,A就是用户。

总之,在C中很多场合,函数可以执行“return NULL;”告诉它的用户“得不到正常的结果”,但有些场合“return xxoo;”无法解决问题,比如上述“int getData(...) { ... }”,在C++中,异常可以解决这个问题,不过用的不是return语句,而是throw语句;用户也不是通过“if (result == NULL)”之类的语句进行判断,而是调用“try-catch”语句。

但异常的作用显然不是这么简单 -- “告诉用户得不到正常的结果”。对于C语言,若执行如下语句:
     int *p = new int;
     delete p;
     delete p;
程序只能崩溃,而C++若使用合适的异常处理这个问题,可以避免程序崩溃,反正异常机制能干这么一种事情,关于如何实现,比较简单,但不是本文的重点,这里引出这个东东只是为了说明异常机制的重要性。

上面都是小case,开始一些稍微有意思的东西吧!
从问题开始:

1. 异常的本质是啥?有啥意义?

异常的本质:内容上文所提到的异常,比如bad_alloc其实是一个类,所以使用throw抛出异常时不能直接执行“throw bad_alloc;”这样的语句,而是先要创建一个bad_alloc对象,所以至少得这样“throw bad_alloc()”;

异常的意义:首先得明白一点,和NULL有一丁点类似,异常是一种通知用户“程序没能正常执行”或“得不到想要的结果”之类的机制,有点类似于函数和它的用户之间的一个约定!

异常机制能修复程序出现的问题吗?No,异常其实是一个非常非常简单的类,它不会对程序做任何实质性的改变,笔者认为从某种角度可以把异常看做C++定义的一种类似于NULL但比NULL要丰富得多的MARK,比如执行new语句失败了,系统会throw一个bad_alloc,用户(调用该new语句的程序)可以根据此错误做些其他事情(比如再执行一次之类的),总之,如何处理这些MAKR,全看用户的catch语句如何处理。

2. 如何抛出异常?

异常对象可能在程序执行期间产生异常时被系统抛出,也可能是某些程序显式执行“throw xxoo;”语句抛出给用户。

P.S:笔者对此并不确定,貌似Java中虚拟机可以抛出异常(可以认为是所谓的“系统”吧),但对于C++,笔者不太确定!或许以后会搞清楚!

3. 异常可以处理所有错误吗?

No,如下:

int main(int argc, char **argv)
{
    string *str = new string;
    try    
    {           
        delete str;         
        delete str;                 
    }                                   
    catch (...)                             
    {                                           
    }   
    cout << "sb" << endl;                                   


    return 0;                                                   
}

编译执行此程序仍然会发生错误,“sb”仍然无法被打印出来。


4. 关于自定义异常~

当然可以自定义异常,public继承exception就可以了。
但是尽量不要自定义异常,《C++标准程序库》是这么说的:
使用非标准类别作为异常将导致程序难以移植,xxoo,所以最好使用标准异常。

但笔者认为,有时候自定义异常还是挺必要的,比如,上文所提到的“int getData(...) { ... }”,若得不到想要的结果时,return语句显然不满足,只能使用throw,但是throw已知的标准异常类对象是不太合适的,因为它们都有特别的意义,使用它们会给用户带来困惑,所以笔者认为此时需要自定义异常。

所有标准异常类的接口只含一个成员函数:what(),用以获取“type本身以外的附加信息”。它返回一个以null结束的字符串:
namespace std{
    class exception {
        public:   
            virtual const char * what() const throw();
            ...                       
    };    
}

5. 异常规格(exception specification)

函数可以随意返回各种异常吗?No,函数若想返回某个异常,需要在其声明式和定义式中添加这么一个东西(见蓝色部分)
void f() throw (bad_alloc, bad_cast) ;
void f() throw (bad_alloc, bad_cast)
{
     throw bad_cast();
     throw bad_alloc();
}

所谓异常规格,用来指明某个函数可能抛出哪些异常。

若函数抛出了没在异常规格中出现的异常会怎如何呢?
《C++标准程序库》中这么说:唤醒unexpected(),后者会唤醒terminated()终止程序。

所以,如下程序:

void f() throw (bad_alloc)
{
    throw bad_cast();
    throw bad_alloc();
}


int main(int argc, char **argv)
{
    try 
    {   
        f();
    }   
    catch(const bad_cast& e)
    {   


    }   
    catch(const bad_alloc& e)
    {


    }
    cout << "hello, world" << endl;
}

执行结果如下:

libc++abi.dylib: terminating with unexpected exception of type std::bad_cast: std::bad_cast 

[1]    3932 abort      ./a.out


“hello, world”还没来得及打印就退出了!

还有一点...
《C++标准程序库》P27里有这么一段话:

然而如果在异常规格中列出bad_exception,那么unexpected()总是会重新抛出(rethrows)bad_exception。

class E1; 
class E2;   // not derived from E1


void f() throw(E1, std::bad_exception)
                // throws exception of type E1 or
                // bad_exception for any other exception type
{
    ... 
    throw E1(); // throws exception of type E1
    ... 
    throw E2(); // throws unexpected(), which throws bad_exception
}

因此,如果异常规格罗列了bad_exception,那么任何未列于规格的异常,都将在函数unexpected()中被代之以bad_exception。

但是,笔者的如下程序:
#include <iostream>
#include <exception>
#include <typeinfo>


using namespace std;


void f() throw(bad_cast, bad_exception)
{
    throw bad_alloc();   // 没有在异常规格中添加bad_alloc
    throw bad_cast();
}


int main(int argc, char ** argv)
{
    try 
    {   
        f();
    }   
    catch (const bad_alloc & e)
    {}  
    catch (const bad_cast & e)
    {}  
    catch (const bad_exception)
    {}  
    cout << "hello, world" << endl;
    return 0;
}

执行结果如下:
Mac  OS 10.9,clang编译器:

libc++abi.dylib: terminating with unexpected exception of type std::bad_alloc: std::bad_alloc 

[1]    4532 abort      ./a.out

Ubuntu,g++编译器:
terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
Aborted (core dumped)

结果显然与书上所说的不太一样,笔者私自揣测,这一论点在现在的C++中已经不实用了。可惜在网上找不到类似的说法。

6. 非标准异常对象不一定继承自exception

《C++标准程序库》P16是这么说的:异常对象(exception objects)其实就是一般类别或基本型别的对象,可以是ints,strings,也可以是类体系中的某个template classes。

好吧,笔者验证一下:
test.cc -- 
#include <iostream>
#include <string>


using namespace std;


void f() throw (string)
{
    throw string("halo");
}

int main(int argc, char **argv)
{
    try 
    {   
        f();
    }   
    catch (const string& e)
    {   
        cout << e << endl;
    }   
    cout << "sb" << endl;


    return 0;
}

执行结果如下:
➜  /Users/zhangwei/forJobs/The C++ Standard Library/03chapter  >./a.out halo sb

啥都不用说了!

----持续更新中

说明:有很多叙述性内容摘抄自以上的书籍,为了避免啰嗦,本文就不啰嗦说明了
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值