C++ 异常处理

本文隶属于分类

编程语言

广告推荐

技术交流学习或者有任何问题欢迎加群

编程技术交流群 : 154514123 爱上编程      Java技术交流群 : 6128790  Java

标签:统一   传播   异常处理   地址   c语言   入口地址   itl   不同   抛出异常   

    本文通过C语言异常处理的方式,引出C++异常处理的概念和做法,进而深入分析C++异常处理机制,和异常处理的特殊处理以及异常规格说明等内容。

1.C语言异常处理

1.1、异常的概念

    异常:程序在运行过程中可能产生异常(是程序运行时可预料的执行分支),如:运行时除0的情况,需要打开的外部文件不存在的情况,数组访问越界的情况...

    Bug:bug是程序中的错误,是不可被预期运行方式,如:野指针、堆内存结束后未释放、选择排序无法处理长度为0的数组...

1.2、C语言的异常处理

    (1)经典方法:

        if(判断是否产生异常)

        {

        //正常代码逻辑

        }

        else

        {

        //异常代码逻辑

        }

#include <iostream>
#include <string>

using namespace std;

double divide(double a, double b, int* valid)
{
    const double delta = 0.000000000000001;  //判断是否非零
    double ret = 0;
    
    if( !((-delta < b) && (b < delta)) )
    {
        ret = a / b;
        
        *valid = 1;
    }
    else
    {
        *valid = 0;
    }
    
    return ret;
}

int main(int argc, char *argv[])
{   
    int valid = 0;
    double r = divide(1, 0, &valid);
    
    if( valid )
    {
        cout << "r = " << r << endl;
    }
    else
    {
        cout << "Divided by zero..." << endl;
    }
    
    return 0;
}


    这种做法的缺陷,写代码时要随时考虑到异常分支。

    (2)setjmp()和longjmp()

        int setjmp(jmp_buf env)

        将当前上下文保存在jmp_buf结构体中

        void longjmp(jmp_buf env, int val)

        从jmp_buf结构体中恢复setjmp()保存的上下文,最终从setjmp函数调用点返回,返回值为val

#include <iostream>
#include <string>
#include <csetjmp>

using namespace std;
/**缺陷:setjmp()和longjmp()的引入必然涉及到全局变量,暴力跳转导致代码可读性降低***/

static jmp_buf env; //定义全局变量

double divide(double a, double b)
{
    const double delta = 0.000000000000001; //一般不要拿浮点数和0直接做比较
    double ret = 0;
    
    if( !((-delta < b) && (b < delta)) )
    {
        ret = a / b;
    }
    else
    {
        longjmp(env, 1);
    }
    
    return ret;
}

int main(int argc, char *argv[])
{   
    if( setjmp(env) == 0 )
    {
        double r = divide(1, 1);
        
        cout << "r = " << r << endl;
    }
    else
    {
        cout << "Divided by zero..." << endl;
    }
    
    return 0;
}

    上述程序的执行流:

        int setjmp(jmp_buf env)//将当前上下文保存在jmp_buf结构体中

        void longjmp(jmp_buf env, int val)

        从jmp_buf结构体中恢复setjmp()保存的上下文,最终从setjmp函数调用点返回,返回值为val

        main函数开始,if判断必然是成立的,然后调用divide函数,当除数为0时,进入else从jmp_buf结构体中恢复setjmp()保存的上下文,最终从setjmp函数调用点返回,返回值为val,此时main函数中的if判断不成立,进入else答应错误信息。

     缺陷:setjmp()和longjmp()的引入必然涉及到全局变量,暴力跳转导致代码可读性降低

2、C++中的异常处理(上)

    (1)C++内置了异常处理的语法元素try、catch、throw

        --try语句处理正常的代码逻辑

        --catch语句处理异常的情况

        --C++通过throw语句抛出异常信息

    try语句中的异常由对应的catch语句处理

        函数在运行时抛出(throw)一个异常到函数调用的地方(try语句内部),try语句就会将异常交给对应的catch语句去处理

#include <iostream>
#include <string>

using namespace std;

/**函数在运行时抛出(throw)一个异常到函数调用的地方(try语句内部),try语句就会将异常交给对应的catch语句去处理**/
double divide(double a, double b)
{
   const double delta = 0.000000000000001;
   double ret = 0;
   
   if( !((-delta < b) && (b < delta)) )
   {
       ret = a / b;
   }
   else
   {
       throw 0;
   }
   
   return ret;
}

int main(int argc, char *argv[])
{    
   try
   {
       double r = divide(1, 0);
           
       cout << "r = " << r << endl;
   }
   catch(...)
   {
       cout << "Divided by zero..." << endl;
   }
   
   return 0;
}


    (2)C++异常处理分析

        --throw抛出的异常必须被catch处理

        当前函数能够处理异常,程序继续往下执行

        当前函数无法处理异常,则函数停止执行,并返回(未被处理的异常会顺着函数调用栈向上传播,直到被处理为止,否则程序将停止执行)

    (3)try语句可以抛出任何类型的异常,catch语句可以定义具体的异常类型

        --不同的异常由不同的catch语句负责处理

        --catch(...)用于处理所有类型的异常(只能被放在最后面)

    注意:任何异常都只能被捕获(catch)一次,异常抛出后,捕获时至上而下将严格的匹配每一个catch语句处理的类型,不进行任何类型的转换

#include <iostream>
#include <string>

using namespace std;

/**异常抛出后,至上而下将严格的匹配每一个catch语句处理的类型,不进行任何类型的转换**/
void Demo1()
{
   try
   {  
       throw 'c';
   }
   catch(char c)
   {
       cout << "catch(char c)" << endl;
   }
   catch(short c)
   {
       cout << "catch(short c)" << endl;
   }
   catch(double c)
   {
       cout << "catch(double c)" << endl;
   }
   catch(...)
   {
       cout << "catch(...)" << endl;
   }
}

void Demo2()
{
   throw string("D.T.Software");
}

int main(int argc, char *argv[])
{    
   Demo1();
   
   try
   {
       Demo2();
   }
   catch(char* s)
   {
       cout << "catch(char *s)" << endl;
   }
   catch(const char* cs)
   {
       cout << "catch(const char *cs)" << endl;
   }
   catch(string ss)
   {
       cout << "catch(string ss)" << endl;
   }
   
   return 0;
}

3、C++中的异常处理(下)

3.1、catch重新解释

    (1)catch中捕获的异常可以被重新解释抛出,catch抛出的异常需要外层的try...catch...捕获

    为什么要重新抛出异常?

    实际工程中我们可以对第三方库中抛出的异常进行捕获、重新解释(统一异常类型,方便代码问题定位),然后再抛出

#include <iostream>
#include <string>

using namespace std;

void Demo()
{
   try
   {
       try
       {
           throw 'c';
       }
       catch(int i)
       {
           cout << "Inner: catch(int i)" << endl;
           throw i;
       }
       catch(...)
       {
           cout << "Inner: catch(...)" << endl;
           throw;
       }
   }
   catch(...)
   {
       cout << "Outer: catch(...)" << endl;
   }
}


/*
   假设: 当前的函数是第三方库中的函数,因此,我们无法修改源代码
   
   函数名: void func(int i)
   抛出异常的类型: int
                       -1 ==》 参数异常
                       -2 ==》 运行异常
                       -3 ==》 超时异常
*/
void func(int i)
{
   if( i < 0 )
   {
       throw -1;
   }
   
   if( i > 100 )
   {
       throw -2;
   }
   
   if( i == 11 )
   {
       throw -3;
   }
   
   cout << "Run func..." << endl;
}

void MyFunc(int i) //调用第三方库函数,捕获并重新解释异常,然后抛出
{
   try
   {
       func(i);
   }
   catch(int i)
   {
       switch(i)
       {
           case -1:
               throw "Invalid Parameter"; //捕获异常并重新解释并抛出
               break;
           case -2:
               throw "Runtime Exception";
               break;
           case -3:
               throw "Timeout Exception";
               break;
       }
   }
}

int main(int argc, char *argv[])
{
   Demo();
   
   try
   {
       MyFunc(11);
   }
   catch(const char* cs)
   {
       cout << "Exception Info: " << cs << endl;
   }
   
   return 0;
}

    注意:

    (1)异常的类型可以是自定义类类型,对于类类型异常的匹配依旧是至上而下、严格匹配

    (2)赋值兼容原则在异常匹配中依然适用,一般而言

        --匹配子类异常的catch放在上部

        --匹配父类异常的catch放在下部

#include <iostream>
#include <string>

using namespace std;

class Base
{
};
//异常的类型可以是自定义类类型
class Exception : public Base
{
   int m_id;
   string m_desc;
public:
   Exception(int id, string desc)
   {
       m_id = id;
       m_desc = desc;
   }
   
   int id() const
   {
       return m_id;
   }
   
   string description() const
   {
       return m_desc;
   }
};


/*
   假设: 当前的函数式第三方库中的函数,因此,我们无法修改源代码
   
   函数名: void func(int i)
   抛出异常的类型: int
                       -1 ==》 参数异常
                       -2 ==》 运行异常
                       -3 ==》 超时异常
*/
void func(int i)
{
   if( i < 0 )
   {
       throw -1;
   }
   
   if( i > 100 )
   {
       throw -2;
   }
   
   if( i == 11 )
   {
       throw -3;
   }
   
   cout << "Run func..." << endl;
}

void MyFunc(int i)
{
   try
   {
       func(i);
   }
   catch(int i)
   {
       switch(i)
       {
           case -1:
               throw Exception(-1, "Invalid Parameter");
               break;
           case -2:
               throw Exception(-2, "Runtime Exception");
               break;
           case -3:
throw Exception(-3, "Timeout Exception");
               break;
       }
   }
}

int main(int argc, char *argv[])
{
   try
   {
       MyFunc(11);
   }

//在定义catch语句块时推荐使用引用作为参数(防止拷贝构造)
// 赋值兼容原则在异常匹配中依然适用,一般而言
   catch(const Exception& e) // 匹配子类异常的catch放在上部
   {
       cout << "Exception Info: " << endl;
       cout << "   ID: " << e.id() << endl;
       cout << "   Description: " << e.description() << endl;
   }
   catch(const Base& e) // 匹配父类异常的catch放在下部
   {
       cout << "catch(const Base& e)" << endl;
   }

   return 0;
}

    (3)工程建议:

        在工程中会定义一系列的异常类,每个类代表工程中可能出现的一种异常类型

        代码复用时可能需要重新解释不同的异常类

        在定义catch语句块时推荐使用引用作为参数(防止拷贝构造)

3.2、标准库异常类族

    (1)C++标准库中提供了实用异常类族,都是从exception类派生的,主要有两个分支

        --logic_error(常用于程序中可避免的逻辑错误)

        --runtime_error(常用于程序中无法避免的恶性错误)

    标准库中的异常:

        技术分享图片


4、函数异常规格说明

    问题1:如何判断一个函数是否会抛出异常,会抛出那些异常?

    C++语法提供了用于申明函数所抛出的异常,异常做为函数声明的 修饰符写函数声明的后面。

        // 可能抛出任何异常

        void fun(void) ;

        // 只能抛出int型异常

        void fun(void) throw(int);

        // 不能抛出异常

        void fun(void) throw();

#include <iostream>

using namespace std;

void func() throw(int)
{
   cout << "func()";
   cout << endl;
   
   throw 'c';
}

int main()
{
   try
   {
       func();
   }
   catch(int)
   {
       cout << "catch(int)";
       cout << endl;
   }
   catch(char)
   {
       cout << "catch(char)";
       cout << endl;
   }

   return 0;
}

    异常规格说明的意义:

        -提示函数调用这必须做好异常处理的准备

        -提示函数的维护者不要抛出其他异常

        -异常规格 说明是函数接口的一部分。

    问题2:如果抛出的异常不在申明列表里会发生什么?

    函数抛出的异常不在规格说明中,全局函数unexpected()会被调用

    默认的unexpected()函数会调用全局的terminate()函数

    可以自定义unexpected()函数

        --函数类型void(*)(void)

        --能够再次抛出异常

    a)在次抛出的异常符合触发函数的异常规格函数时,程序恢复执行

    b)否则,调用terminate()函数结束程序

    --调用set_unexpected()函数设置自定义的异常函数,返回值为默认的unexpected函数入口地址。

#include <iostream>
#include <cstdlib>
#include <exception>

using namespace std;

void my_unexpected()
{
   cout << "void my_unexpected()" << endl;
   // exit(1);
   throw 1;
}

void func() throw(int)
{
   cout << "func()";
   cout << endl;
   
   throw 'c';
}

int main()
{
   set_unexpected(my_unexpected);
   
   try
   {
       func();
   }
   catch(int)
   {
       cout << "catch(int)";
       cout << endl;
   }
   catch(char)
   {
       cout << "catch(char)";
       cout << endl;
   }

   return 0;
}

    注意不是所有C++编译器都支持这个标准行为,其中vs就不支持(会直接处理抛出的异常,尽管其不在异常规格申明中)。

5、try ...catch的其他写法

    try…catch用于分隔正常逻辑的代码和异常处理代码,可以直接将函数实现分隔为两部分。

#include <iostream>
#include <string>

using namespace std;

int func(int i, int j) throw(int, char)
{
   if( (0 < j) && (j < 10) )
   {
       return (i + j);
   }
   else
   {
       throw '0';
   }
}

void test(int i) try
{
   cout << "func(i, i) = " << func(i, i) << endl;
}
catch(int i)
{
   cout << "Exception: " << i << endl;
}
catch(...)
{
   cout << "Exception..." << endl;
}


int main(int argc, char *argv[])
{
   test(5);
   
   test(10);
   
   return 0;
}

    显然这种写法代码可读性降低。但时在一些老的代码可能会碰到这种写法。


    本文参考自唐老师课程,特此鸣谢。


C++ 异常处理

标签:统一   传播   异常处理   地址   c语言   入口地址   itl   不同   抛出异常   

原文:http://blog.51cto.com/11134889/2067752

技术交流学习或者有任何问题欢迎加群

编程技术交流群 : 154514123 爱上编程      Java技术交流群 : 6128790  Java

广告推荐

讨论区