美文网首页
C++中的异常处理

C++中的异常处理

作者: nethanhan | 来源:发表于2017-10-14 11:08 被阅读0次

    C语言异常处理

    异常的概念

    • 异常的说明
      • 程序在运行过程中可能产生异常
      • 异常(Exception)与Bug的区别
        • 异常是程序运行时可预料的执行分支
        • Bug是程序中的错误,是不被预期的运行方式
    • 异常(Exception)和Bug的对比:
      • 异常
        • 运行时产生除0的情况
        • 需要打开的外部文件不存在
        • 数组访问时越界
      • Bug
        • 使用野指针
        • 堆数组使用结束后未释放
        • 选择排序无法处理长度为0的数组

    异常处理

    C语言经典处理方式:if ... else ...

    void func()
    {
        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;
    }
    

    运行结果为:

    Divided by zero...
    

    这种做法也有缺陷:

    • divide函数有3个参数,难以理解其用法
    • divide函数调用后必须判断valid代表的结果
      • 当valid为true时,运算结果正常
      • 当valid为false时,运算过程出现异常

    所以这里再介绍另外一种方法:

    • 通过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;
    
    //创建一个静态的结构体
    static jmp_buf env;
    
    double divide(double a, double b)
    {
        const double delta = 0.000000000000001;
        double ret = 0;
        
        if( !((-delta < b) && (b < delta)) )
        {
            ret = a / b;
        }
        else
        {
            //假如程序执行到这里,就恢复setjmp保存的上下文,然后返回1
            longjmp(env, 1);
        }
        
        return ret;
    }
    
    int main(int argc, char *argv[])
    {   
        //将当前的上下文保存到env结构体中
        if( setjmp(env) == 0 )
        {
            double r = divide(1, 0);
            
            cout << "r = " << r << endl;
        }
        else
        {
            //返回1以后,与0不相等,就执行到这里
            cout << "Divided by zero..." << endl;
        }
        
        return 0;
    }
    

    执行结果为:

    Divided by zero...
    

    这种方法的缺陷是:

    • setjmp( )和longjmp( )的引入
      • 必然涉及到使用全局变量
      • 暴力跳转导致代码可读性降低
      • 本质还是if ... else ... 异常处理方式

    最后再介绍一种方法,还是用 if ... else ... 来做,但是使用宏定义来说明语义:

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    #define SUCCESS           0 
    #define INVALID_POINTER   -1
    #define INVALID_LENGTH    -2
    #define INVALID_PARAMETER -3
    
    int MemSet(void* dest, unsigned int length, unsigned char v)
    {
        if( dest == NULL )
        {
            return INVALID_POINTER;
        }
        
        if( length < 4 )
        {
            return INVALID_LENGTH;
        }
        
        if( (v < 0) || (v > 9) )
        {
            return INVALID_PARAMETER;
        }
        
        unsigned char* p = (unsigned char*)dest;
        
        for(int i=0; i<length; i++)
        {
            p[i] = v;
        }
        
        return SUCCESS;
    }
    
    int main(int argc, char *argv[])
    {
        int ai[5];
        int ret = MemSet(ai, sizeof(ai), 0);
        
        if( ret == SUCCESS )
        {
        }
        else if( ret == INVALID_POINTER )
        {
        }
        else if( ret == INVALID_LENGTH )
        {
        }
        else if( ret == INVALID_PARAMETER )
        {
        }
        
        return ret;
    }
    

    小结

    • 程序中不可避免的会发生异常
    • 异常是在开发阶段就可以预见的运行时问题
    • C语言中通过经典的 if ... else ... 方式处理异常
    • C++中存在更好的异常处理方式

    C++的异常处理

    异常处理介绍

    C++内置了异常处理的语法元素 try ... catch ...

    • try语句处理正常代码逻辑
    • catch语句处理异常情况
    • try语句中的异常由对应的catch语句处理
    • 语法:
    try
    {
        double r = divide(1, 0);
    }
    catch(...)
    {
        cout << "Divided by zero..." << endl;
    }
    
    • C++通过throw语句抛出异常信息
    double divide(double a, double b)
    {
        const double delta = 0.0000000000001;
        double ret = 0;
        if(!((-delta < b) && (b < delta)))
        {
            ret = a / b;
        }
        else
        {
            //产生除0异常
            throw 0;
        }
    
        return ret;
    }
    

    异常处理分析

    • 这里对C++异常处理分析一下:
      • throw抛出的异常必须被catch处理
        • 当前函数能够处理异常,程序继续往下执行
        • 当前函数无法处理异常,则函数停止执行,并返回

    未被处理的异常会顺着函数调用栈向上传播,知道被处理为止,否则程序将停止执行。

    function1 ==> function2 ==> function3
              <==           <== throw1;
    

    这里举一个例子:

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    double divide(double a, double b)
    {
        const double delta = 0.000000000000001;
        double ret = 0;
        
        if( !((-delta < b) && (b < delta)) )
        {
            ret = a / b;
        }
        else
        {
            //用throw抛出异常信息
            throw 0;
        }
        
        return ret;
    }
    
    int main(int argc, char *argv[])
    {    
        try
        {
            double r = divide(1, 0);
                
            cout << "r = " << r << endl;
        }
        catch(...)
        {
            //try语句中抛出的异常在这里接受并处理
            cout << "Divided by zero..." << endl;
        }
        
        return 0;
    }
    

    继续学习 try... catch ...的知识点:

    • 同一个try 语句可以跟上多个catch语句

      • catch语句可以定义具体处理的异常类型
      • 不同类型的异常由不同的catch语句负责处理
      • try语句中可以抛出任何类型的异常
      • catch( ... ) 用于处理所有类型的异常
      • 任何异常都只能被捕获(catch)一次
    • 异常处理的匹配规则:

    //异常处理匹配时,不进行任何的类型转换
    |   try
    |   {
    |       throw 1;
    |   }   
    |   catch(Type1 t1)
    |   {
    |   }
    |   catch(Type2 t2)
    |   {
    |   }
    |   catch(TypeN tn)
    |   {
    |   }
    v
    异常抛出后,自上而下严格匹配每一个catch语句处理的类型
    

    这里用一个例子来试验一下匹配规则:

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    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()
    {
        //这里抛出string类字符串
        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;
    }
    

    执行结果如下:

    catch(char c)
    catch(string ss)
    

    catch再抛出异常

    在try ... catch ... 语句中,catch语句块中可以抛出异常

    try
    {
        func();
    }
    catch(int i)
    {
        //将捕获的异常重新抛出
        throw i;
    }
    catch(...)
    {
        //将捕获的异常重新抛出
        throw;
    }
    
    //catch中抛出的异常需要外层的try ... catch ...捕获
    

    可是为什么要重新抛出异常呢?因为:

    catch中捕获的异常可以被重新解释后抛出,这样就可以在工程开发中使用这样的方式统一异常类型。
    
    //工程开发
    通过调用 MyFunc 获得 func函数的功能和统一的异常信息
        |
        | 调用
        V
    
    //私有库
    void MyFunc(int i);
    /*异常类型为Exception*/
        |
        | 封装
        V
    
    //第三方库
    void func(int i);
    /*异常类型为int*/
    

    这里举一个例子:

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    void Demo()
    {
        
        try
        {
            try
            {
                //这里抛出的异常由内层try ... catch ...来捕获处理
                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;
    }
    

    运行结果为:

    Inner: catch(...)
    Outer: catch(...)
    Exception Info: Timeout Exception
    

    自定义异常类型

    自定义类类型及匹配:

    • 异常的类型可以是自定义类类型
    • 对于类类型异常的匹配依旧是自上而下严格匹配
    • 赋值兼容性原则在异常匹配中依然适用
    • 一般而言
      • 匹配子类异常的catch放在上部
      • 匹配父类异常的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(const Exception& e)
        {
            cout << "Exception Info: " << endl;
            cout << "   ID: " << e.id() << endl;
            cout << "   Description: " << e.description() << endl;
        }
        //接受到的时候 判断为引用类型
        catch(const Base& e)
        {
            cout << "catch(const Base& e)" << endl;
        }
        
        return 0;
    }
    

    运行结果为:

    Exception Info: 
       ID: -3
       Description: Timeout Exception
    

    C++标准库的异常类族

    在C++标准库中提供了实用异常类族

    • 标准库中的异常都是从 exception 类派生的
    • exception 类有两个主要的分支
      • logic_error
        • 常用于程序中的可避免逻辑错误
      • runtime_error
        • 常用于程序中无法避免的恶性错误

    这里演示一下如何使用:

    #include <iostream>
    #include <string>
    #include "Array.h"
    #include "HeapArray.h"
    
    using namespace std;
    
    void TestArray()
    {
        Array<int, 5> a;
        
        /*
            这里如果越界会抛出异常:
            throw out_of_range("T& Array<T, N>::operator[] (int index)");
            throw out_of_range("T Array<T, N>::operator[] (int index) const");
            */
        for(int i=0; i<a.length(); i++)
        {
            a[i] = i;
        }
            
        for(int i=0; i<a.length(); i++)
        {
            cout << a[i] << endl;
        }
    }
    
    void TestHeapArray()
    {
        HeapArray<double>* pa = HeapArray<double>::NewInstance(5);
        
        if( pa != NULL )
        {
            HeapArray<double>& array = pa->self();
            
            /*
            这里如果越界会抛出异常:
            throw out_of_range("T& HeapArray<T>::operator [] (int index)");
            throw out_of_range("T HeapArray<T>::operator [] (int index) const");
            */
            for(int i=0; i<array.length(); i++)
            {
                array[i] = i;
            }
                
            for(int i=0; i<array.length(); i++)
            {
                cout << array[i] << endl;
            }
        }
        
        delete pa;
    }
    
    int main(int argc, char *argv[])
    {
        
        try
        {
            TestArray();
            
            cout << endl;
            
            TestHeapArray();
        }
        catch(...)
        {
            cout << "Exception" << endl;
        }
        
        return 0;
    }
    

    小结

    • C++中直接支持异常处理的概念
    • try ... catch ...是C++中异常处理的专用语句
    • try 语句处理正常代码逻辑,catch 语句处理异常情况
    • 同一个 try 语句可以跟上多个 catch 语句
    • 异常处理必须严格匹配,不进行任何的类型转换
    • catch语句块中可以抛出异常
    • 异常的类型可以是自定义类类型
    • 赋值兼容性原则在异常匹配中依然适用
    • 标准库中的异常都是从exception类派生的

    相关文章

      网友评论

          本文标题:C++中的异常处理

          本文链接:https://www.haomeiwen.com/subject/hxexuxtx.html