CPP_异常处理

1. 基础

处理错误有两种方式返回值和异常,C语言是典型的返回值凡是,C++包含两种。
返回值处理错误缺点:

  • 程序员经常「忘记」处理错误返回值
  • 每个可能产生错误的函数在调用后都需要判断是否有错误
  • 与「真正的」返回值混用,需要规定一个错误代码(通常是0、-1或NULL)

异常处理错误的缺点:

  • 使控制流变得复杂,难以追踪
  • 开销相对较大,需要构建异常类

异常处理错误的优点:

  • 错误信息丰富,便于获得错误现场
  • 代码相对简短,不需要判断每个函数的返回值

C++异常特点:

  • 异常的处理流程是完全独立的,throw 抛出异常后就可以不用管了,错误处理代码都集中在专门的 catch 块里。这样就彻底分离了业务逻辑与错误逻辑,看起来更清楚。
  • 异常是绝对不能被忽略的,必须被处理。如果你有意或者无意不写 catch 捕获异常,那么它会一直向上传播出去,直至找到一个能够处理的 catch 块。如果实在没有,那就会导致程序立即停止运行,明白地提示你发生了错误,而不会“坚持带病工作”。
  • 异常可以用在错误码无法使用的场合,这也算是 C++ 的“私人原因”。因为它比 C 语言多了构造 / 析构函数、操作符重载等新特性,有的函数根本就没有返回值,或者返回值无法表示错误,而全局的 errno 实在是“太不优雅”了,与 C++ 的理念不符,所以也必须使用异常来报告错误。

几个应当使用异常的判断准则:

  • 不允许被忽略的错误;
  • 极少数情况下才会发生的错误;
  • 严重影响正常流程,很难恢复到正常状态的错误;
  • 无法本地处理,必须“穿透”调用栈,传递到上层才能被处理的错误。

2. 异常使用

异常是程序在执行期间产生的问题。C++异常处理使用try...throw...catch...
异常通过类型来捕获

抛出异常

可以使用throw关键字在代码块中任何地方抛出异常。throw语句的操作数可以使任意的表达式,表达式的结果类型决定了抛出的异常的类型。

捕获异常

catch块跟在try块后面,用于捕获异常。通过在catch中指定异常类型,来捕获特定的异常。

try{
    c[10] = 3;
    cout <<"work done."<<endl;
    throw 1;
}
catch(int exception){
    if(exception == 1){
        cerr<<"out of range."<<endl;
    } else {
        cerr<<"int other error."<<endl;
    }

}catch(double){
    cerr<<"other error."<<endl;
}

捕获所有异常(catch-all):为了一次性捕获所有异常,我们使用省略号作为异常声明,形如catch(...)。一条捕获所有异常的语句可以与任意类型的异常匹配。

异常捕获后,不会返回到异常发生的地方继续执行,而是执行捕获后的语句。

function-try

把整个函数体视为一个大 try 块,而 catch 块放在后面,与函数体同级并列。

void some_function()
try                          // 函数名之后直接写try块
{
    ...
}
catch(...)                   // catch块与函数体同级并列
{
    ...
}

异常对象

C++提供了一系列标准的异常,定义在中。std::exception是所以异常类的父类,派生的子类有std::bad_alloc,std::bad_cast, std::bad_typeid, std::bad_exception, std::logic_error, std::runtime_error,其中std::runtime_error又派生出std::overflow_error, std::range_error, std::underflow_error。

异常类提供了what()方法,返回异常产生的原因。

virtual const char* what() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_NOTHROW;

异常对象(exception object)是一种特殊的对象,编译器使用异常抛出表达式来对异常对象进行拷贝初始化。当我们抛出一条表达式时,该表达式的静态编译时类型决定了异常对象的类型。

class my_exception : public std::runtime_error
{
public:
    using this_type     = my_exception;        // 给自己起个别名
    using super_type    = std::runtime_error;  // 给父类也起个别名
public:
    my_exception(const char* msg):            // 构造函数
        super_type(msg)                      // 别名也可以用于构造
    {}  

    my_exception() = default;                // 默认构造函数
   ~my_exception() = default;                // 默认析构函数
private:
    int code = 0;                            // 其他的内部私有数据
};

注意 析构函数不应该跑出异常

析构函数总是会被执行,但是普通函数中负责释放资源的代码却可能被跳过,这一特点对于我们如何组织程序结构与重要影响。如果一个块分配了资源,并且在负责释放资源的代码前发生了异常,则释放资源的代码将不会执行。另一方面,类对象分配的资源将由类的析构函数负责释放。因此,如果我们使用类来控制资源的分配,就能确保无论函数正常结束还是遭遇异常,资源都能被正常释放。

出于栈展开可能使用析构函数的考虑,析构函数不应该抛出不能被它自身处理的异常。换句话说,如果析构函数需要执行某个可能爆出异常的操作,则该操作应该被放置在一个try语句块当中,并且在析构函数内部得到处理。

在实际的编程过程中,因为析构函数仅仅是释放资源,所以它不太可能抛出异常。所有标准库类型都能确保它们的析构函数不会引发异常。

注意 声明函数抛出异常

const char * what () const throw ()
{
//函数体
}
  1. const char * 表示返回值类型
  2. what 是函数名称
  3. () 是参数列表
  4. const 表示该成员函数不能修改成员变量
  5. throw() 是异常规格说明符。括号内写该函数可抛出的异常类型,这里面没有类型,就是声明这个函数不抛出异常,通常函数不写后面的就表示函数可以抛出任何类型的异常。

在 C++11 中,声明一个函数不可以抛出任何异常使用关键字 noexcept。

void mightThrow(); // could throw any exceptions.
void doesNotThrow() noexcept; // does not throw any exceptions.

下面两个函数声明的异常规格在语义上是相同的,都表示函数不抛出任何异常。

void old_stytle() throw();
void new_style() noexcept;

3. 示例

#include <iostream>
#include <string>

using namespace std;

void test()
{
    string path = "/tmp/wang";
    char *str = (char *)"STR";

    if(!path.empty()){
//        throw string("can't open " + path);
        throw std::runtime_error(static_cast<string>(str) +  " can't open");
    }

	cout <<"After exception" << endl;
}

int main()
{
    try{
        test();
    } catch(const char* msg){
        cerr <<"Char*: "<< msg << endl;
    } catch(string &str){
        cerr << "STR: "<<str <<endl;
    } catch(exception& e){
        cerr << "Exception: "<< e.what() << endl;
    }

	cout << "main over" <<endl;
    return 0;
}
------------
Exception: STR can't open
main over

参考

  1. C++ 异常处理 runoob
  2. 如何处理C++构造函数中的错误——兼谈不同语言的错误处理_转
  3. 罗剑锋的C++实战笔记 - 极客时间
posted @ 2018-09-09 22:05  yuxi_o  阅读(674)  评论(0编辑  收藏  举报