• C++异常处理


    C++异常处理


    1.C语言与C++对于异常的处理方式

    1.C语言对于异常的处理:

    • 方法一:在之前我们遇到一些bug的时候,通常会用if判断或者assert断言等问题进行处理。但这种方式太过暴力,会直接中断程序的运行
    • 方法二:返回错误码,C语言的报错大多使用这种方式。不过这需要程序的用户自己去查对应的错误码表格,较为麻烦

    2.C++对于异常的处理:

    • C++标准库中便使用了一个exception类来进行异常的处理,我们运行程序中遇到的一些报错,其实就是标准库里面抛出了对应的异常

    请添加图片描述

    其操作主要借助下面三个关键字:

    1. throw:在出现问题的地方抛出异常(throw关键字可以抛出任意类型的异常)
    2. try:监控后续代码中出现的异常,后续需要以catch作为结尾
    3. catch:用于捕获异常,同一个try可以用多个不同类型的catch进行捕获
    int Div()
    {
    	int a, b;
    	cin >> a >> b;
    	if (b == 0)
    		throw "div 0 err!";
    	return a / b;
    }
    
    int main()
    {
    	try 
        {
    		cout << Div() << endl;
    	}
    	catch(const char* s)
        {
    		cout << s << endl;
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    请添加图片描述


    2.异常处理的基本操作

    2.1 异常处理的细节

    我们在进行异常处理的时候需要注意三点,否则容易出问题:

    1. catch类型对应
    2. 利用…进行全捕获
    3. 基类捕获派生类的异常
    1. catch类型对应

      • 当我们进行抛异常的时候,一定需要有对应类型的catch,否则会报错

      • 比如我们throw的是一个常量字符串,如果用string来catch,就会因为类型不匹配而出现报错

    请添加图片描述

    • 所以当我们使用某一个会抛异常的函数的时候,一定要注意其抛出异常的类型

    请添加图片描述

    1. 利用…进行全捕获

      • 假设我们不知道这里面会抛出什么类型的错误呢?总不能把所有类型都catch一下吧?

      • 当然不需要,我们可以使用下面的函数进行全捕获

    请添加图片描述

    • 这就可以用于当我们不知道报错类型的时候。不过一般的使用场景是,在这之前先catch已知的错误类型,最后再加上一个全捕或,作为未知错误的标识
      • 不过catch(...)有一个缺点,那便是我们不能知道异常的类型
    1. 基类捕获派生类的异常

      • 当我们出现异常的时候,如果throw了一个子类对象,可以用基类的引用来接收!

      • 这个在进行继承多态的错误编写的时候就很有用

        class A 
        {
        	int a;
        };
        class B : public A
        {
        	int b;
        public:
        	B()
        		:b(1)
        	{}
        };
             
        void testab()
        {
        	B bt;
        	throw bt;
        }
             
        int main()
        {
        	try 
            {
        		testab();
        	}
        	catch (A& e) 
            {
        		cout << "err class A" << endl;
        	}
        	catch (...)
            {
        		cout << "err" << endl;
        	}
             
        	return 0;
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36

    请添加图片描述


    2.2 异常抛出和匹配问题

    异常抛出规则:

    1. 异常时通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码
    2. 被选中的处理代码的调用链是,找到于该类型匹配且离抛出异常位置最近的那一个catch
    3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象
    4. catch(…)可以捕获任意类型的对象,主要是用来捕获没有显示捕获类型的异常,因为如果没有匹配的catch会终止程序。相当于条件判断中的else。问题是不知道异常错误是什么
    5. 实际中抛出和捕获的类型不一定类型完全匹配,可以抛出派生类对象,使用基类来捕获,这个在实际生活中很实用。主要原因是:派生类可以赋值给基类

    请添加图片描述

    异常的匹配规则:

    1. 首先检查throw本身是否在try块内部,如果是,再在当前函数栈中查找匹配的catch语句。如果有匹配的直接跳到catch的地方执行
    2. 如果没有匹配的catch块,则退出但钱函数栈,在调用函数的栈中查找匹配的catch
    3. 如果到达main函数的栈,都没有匹配的catch,就会终止程序
    4. 上述沿着调用链查找匹配的catch块的过程叫栈展开。所以实际要最后要加一个catch(…)来捕获任意类型的异常,防止程序终止
    5. 找到匹配的catch会直接跳到catch语句执行,执行完后,会继续沿着catch语句后面执行

    比如:

    请添加图片描述

    如果在中间匹配的:

    请添加图片描述

    结论:按照函数调用链,一层一层往外找,直到找到匹配的catch块,直接跳到匹配的catch块执行,执行完catch,会继续往catch块后面的语句执行。相当于没有找到匹配的函数栈帧被释放了


    2.3 异常的重新抛出

    有可能单个的catch不能完全处理一个异常,在进行一些矫正处理后,需要交给更外层的调用链函数来处理。catch可以做完矫正操作,再将异常重新抛出,交给更上层的函数进行处理

    请添加图片描述


    3.异常处理的常见问题

    3.1 异常安全问题

    由于抛异常只要找到匹配的catch就直接跳到catch块执行,没有找到对应catch的函数就不会继续执行。这样导致函数的执行流回很乱。可能会导致一些问题:

    1. 构造函数完成对象的构造和初始化,最好不要再构造函数中抛出异常,否则可能导致对象不完整或者没有完全初始化
    2. 析构函数主要完成资源的清理,最好不要在析构函数中抛异常,否则可能导致内存泄漏
    3. C++异常经常会导致资源泄漏问题。比如:在new和delete中抛出异常,导致new出来的资源没有释放,导致内存泄漏。在lock和unlock中抛出异常,导致锁没有释放,导致死锁

    有两种解决办法:

    1. 将异常捕获,释放资源后,将锁重新抛出
    2. 使用RAII(资源获取就是初始化)的思想解决。定义一个类封装,管理资源。当要使用时实例化一个类对象,将资源传入,当退出函数,调用对象析构函数,释放资源

    3.2 异常规范说明问题

    1. 异常规格说明,是使函数调用者直到函数可能会抛出哪些异常。可以在函数后面接throw(异常类型),列出这个函数可能抛出的所有异常类型

    2. 在函数后面加throw()或者noexcept表示不抛异常

    3. 若没有接口声明表示,此函数可能会抛出任意类型的异常

      // 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
      void fun() throw(A,B,C,D);
         
      // 这里表示这个函数只会抛出bad_alloc的异常
      void* operator new (std::size_t size) throw (std::bad_alloc);
         
      // 这里表示这个函数不会抛出异常
      void* operator new (std::size_t size, void* ptr) throw();
      void* operator new (std::size_t size, void* ptr) noexcept;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

    3.3 自定义异常类型问题

    1. 在实际中,并不是我们想抛什么异常就抛什么异常,这样会导致捕捉的时候不好捕捉。实际使用中,会建立一个继承体系,建立一个异常类,派生类继承这个类,来定义出不同的异常
    2. 到时候抛出异常,只需要用基类进行捕捉即可

    请添加图片描述

    基类可以相当于是一个框架,派生类是具体的异常。然后去具体实现异常的内容,然后抛异常只需要抛派生类,捕捉异常只需要捕捉基类即可

    //基类
    //异常
    class Exception
    {
    public:
    	
    	Exception(const char* str = nullptr, int id = 0)
    		:_errmsg(str)
    		, _id(id)
    	{}
    	virtual void what()const = 0;
    protected:
    	string _errmsg;//错误信息
    	int _id;//错误码
    };
    
    //派生类
    //数据库异常
    class SqlException :public Exception
    {
    public:
    	SqlException(const char *str = nullptr, int id = 1)
    		:Exception(str, id)
    	{}
     
    	virtual void what()const
        {
    		cout << "error msg:" << _errmsg << endl;
    		cout << "error id:" << _id << endl;
    	}
    };
    
    //网络异常
    class HttpException :public Exception
    {
    public:
    	HttpException(const char *str = nullptr, int id = 2)
    		:Exception(str, id)
    	{}
     
    	virtual void what()const
        {
    		cout << "error msg:" << _errmsg << endl;
    		cout << "error id:" << _id << endl;
    	}
    };
    
    //缓存异常
    class CacheException :public Exception
    {
    public:
    	CacheException(const char *str = nullptr, int id = 3)
    		:Exception(str, id)
    	{}
     
    	virtual void what()const
        {
    		cout << "error msg:" << _errmsg << endl;
    		cout<< "error id:" << _id << endl;
    	}
    };
     
    void test()
    {
    	//当网络连接失败,抛出这个异常即可
    	//throw HttpException("Http fail", 2);
    	//当缓存错误
    	//throw CacheException("Cache error", 3);
    	//当数据库错误
    	throw SqlException("Sql error", 4);
    }
     
    int main()
    {
    	try
        {
    		test();
    	}
        //用基类捕捉即可
    	catch (const Exception& a)
        {
    		a.what();
    	}
    	catch (...)
        {
    		cout << "unknow exception" << endl;
    	}
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    4.C++标准库中的异常体系

    在C++标准库中,异常是围绕下图组织的

    请添加图片描述

    请添加图片描述

    unsigned short test()
    {
    	unsigned short a = 0;
    	unsigned short b = 0;
    	cin >> a >> b;
    	if (a + b > 65535 )
        {
    		//c++标准库中的异常类
    		throw overflow_error("overflow");
    	}
    	return a + b;
    }
     
     
    int main()
    {
    	try
        {
    		printf("%d\n", test());
    	}
    	//用基类捕捉
    	catch (const exception& a)
        {
    		cout << a.what() << endl;
    	}
    	catch (...)
        {
    		cout << "unknow exception" << endl;
    	}
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    请添加图片描述


    5.异常的优缺点总结

    异常的优点:

    1. 异常对象定义完备之后,相比于错误码的方式,能让用户更加清楚的了解到自己遇到了什么类型的问题,更好定位程序的bug
    2. 函数错误码若遇到,需要层层向外返回;而异常则通过catch可以直接跳到对应处理位置
    3. 第三方库包含异常,我们在使用类似于boost/gtest等第三方库的时候也需要使用对应的异常处理
    4. 对于T& operator[]这种操作符重载,我们没办法很好地使用返回值来标识错误(因为不同类型的返回值不一样,没办法统一处理)这时候就可以用异常来抛出越界问题

    异常的缺点:

    1. 异常可能会导致程序到处乱跳(因为会跳到最近的catch位置)给观察错误情况增添了一些难度
    2. 异常有一定性能开销(可忽略)
    3. 异常容易导致资源泄漏等等问题
    4. 异常依赖于用户编程规范,否则函数调用容易出现异常没有得到处理的问题

    C++相比于其他语言异常机制问题:

    1. 其他语言与C++异常机制基本差不多
    2. C++标准库定义的不好,很多公司都是自己定义一套自己的异常库
    3. C++没有垃圾回收器,所以申请和释放内存要非常小心,容易泄露,需要RAII机制来处理
  • 相关阅读:
    基于准确度评估的7自由度手术机器人术前摆位优化算法
    kubernetes负载均衡部署
    VMware中安装WindowsXP虚拟机详细步骤
    金仓数据库 KingbaseES V8 GIS数据迁移方案(3. 基于ArcGIS平台的数据迁移到KES)
    Route53 – Part 1
    【24届数字IC秋招总结】正式批面试经验汇总9——飞腾
    Html飞机大战(十七): 优化移动端
    SpringBoot 实现EMQ设备的上下线告警
    LQ0026 修剪灌木【数学】
    java.nio.file.FileSystemException:......xx.jar
  • 原文地址:https://blog.csdn.net/qq_29678157/article/details/127845941