• 初识exception


    异常可抛出的类型(事实上,可以抛出任何类型):
    1.基本类型及复合类型;
    2.类类型

    使用(类)对象作为异常抛出的好处:
    1.对象的类名称可以传递信息;
    2.对象可以存储信息,包括用于描述异常的字符串.
    (备注:建议按const引用捕获异常)

    如果存在未捕获的异常,会调用内建的terminate()函数,这个函数调用中的abort()终止程序

    没有抛出列表的函数可以抛出任何异常。具有noexcept或者空抛出列表throw()的函数不能抛出任何异常。

    1.异常示例

    #pragma once
    
    #include <stdexcept>
    #include <exception>
    #include <iostream>
    
    namespace test_exception {
        float divide1(float a, float b) {
            if(b == 0){
                //throw "divided by zero.";
                //throw std::invalid_argument("Divide by zero");
                //throw std::exception();
                throw 5;
            }
            return a / b;
        }
        
        float divide2(float a, float b) noexcept/*throw()*/{
            if(b == 0){
                //throw "divided by zero.";
                throw std::invalid_argument("std::invalid_argument, Divide by zero!");
                //throw std::exception();
                //throw 5;
            }
            return a / b;
        }
        
    #if 1
        float divide3(float a, float b) throw(int, std::invalid_argument){  // 表示只可抛出int, std::invalid_argument两种类型的异常
            if(b == 0){
                //throw "divided by zero.";  // 此种类型无法抛出
                throw std::invalid_argument("std::invalid_argument, Divide by zero!"); // 可抛出此种类型
                //throw std::exception();   // 此种类型无法抛出
                //throw 5;                  // 可抛出此种类型
            }
            return a / b;
        }
    #endif
    
        auto catchSample1() -> void {
            int a = 10;
            int b = 0;
    
            try {
                std::cout << divide1(a, b) << std::endl;
            }
            catch(const char* e){
                 std::cout << e << std::endl;
            }
            catch(const std::invalid_argument& e){
                std::cout << e.what() << std::endl;
            }
            catch(const std::exception& e){  // 增加const属性不会影响匹配的目的,即此句与std::exception& e均可匹配std::exception类型的异常
                std::cout << "+++" << e.what() << "+++" << std::endl;
            }
            catch(int e){
                std::cout << "catching integer\n";
            }
            catch(...){  // 三个点时与所有异常类型匹配的通配符,可以使用这一技术确保捕获所有可能的异常
                std::cout << "unknow exception" << std::endl;
            }
        }
        
        auto catchSample2() -> void {
            int a = 10;
            int b = 0;
    
            std::cout << "entering catchSample2()...............\n";
            try {
                std::cout << divide2(a, b) << std::endl;
            }
            catch(const std::invalid_argument& e){
                std::cout << e.what() << std::endl;
            }
            catch(...){
                std::cout << "unknow exception" << std::endl;
            }
            std::cout << "exiting catchSample2()...............\n";
        }    
    
        auto main() -> void {
            std::cout << "testing exception......" << std::endl;
            
            catchSample1();
            catchSample2();
            
            std::cout << "--------------------------------------\n";
        }
    }
    
    • 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

    float divide2(float a, float b) noexcept定义成禁止抛出异常,因此当除以0(异常发生)时在catchSample2() 并不进行捕获,而是交由操作系统处理(一般是调用terminate())。
    以上程序输出:

    __cplusplus: 201103
    testing exception......
    catching integer
    entering catchSample2()...............
    terminate called after throwing an instance of 'std::invalid_argument'
      what():  std::invalid_argument, Divide by zero!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    需要注意的是对于形如上述代码中float divide3(float a, float b) throw(int, std::invalid_argument)抛出列表C++17已不再支持,因此如果使用C++17标准编译会报如下错误:
    error: ISO C++17 does not allow dynamic exception specifications

    # specify the C++ standard
    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_STANDARD_REQUIRED True)
    
    • 1
    • 2
    • 3

    2.异常层次结构

    ellipse
    图1 C++标准异常完整层次结构

    在类层次结构中捕获异常时需要注意:
    1.利用多态捕获异常时,一定要按引用捕获。如果按值捕获异常就可能发生截断,此时丢失对象的信息。
    2.catch语句应该按照限制减弱的顺序出现(特殊放在前、一般放在后)。例如如果想捕获invalid_argument异常,那么就应该将invalid_argument放在exception前面;如果invalid_argument放在exception后面那么invalid_argument永远无法执行。详见下面两个示例:

    #pragma once
    
    #include <stdexcept>
    #include <exception>
    #include <iostream>
    
    namespace test_exception {
        float divide(float a, float b) {
            if(b == 0){
                throw std::invalid_argument("Divide by zero");
                //throw std::exception();
            }
            return a / b;
        }
        
        auto catchSample() -> void {
            int a = 10;
            int b = 0;
    
            try {
                std::cout << divide(a, b) << std::endl;
            }
            catch(const std::invalid_argument& e){
                std::cout << e.what() << std::endl;
            }
            catch(const std::exception& e){
                std::cout << "+++" << e.what() << "+++" << std::endl;
            }
            catch(...){
                std::cout << "unknow exception" << std::endl;
            }
        }
     
        auto main() -> void {
            std::cout << "testing exception......" << std::endl;
            
            catchSample();
            
            std::cout << "--------------------------------------\n";
        }
    }
    
    • 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

    执行结果如下:

    __cplusplus: 201703
    testing exception......
    Divide by zero
    --------------------------------------
    The end.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    #pragma once
    
    #include <stdexcept>
    #include <exception>
    #include <iostream>
    
    namespace test_exception {
        float divide(float a, float b) {
            if(b == 0){
                throw std::invalid_argument("Divide by zero");
                //throw std::exception();
            }
            return a / b;
        }
        
        auto catchSample() -> void {
            int a = 10;
            int b = 0;
    
            try {
                std::cout << divide(a, b) << std::endl;
            }
            catch(const std::exception& e){
                std::cout << "+++" << e.what() << "+++" << std::endl;
            }
            catch(const std::invalid_argument& e){
                std::cout << e.what() << std::endl;
            }        
            catch(...){
                std::cout << "unknow exception" << std::endl;
            }
        }
     
        auto main() -> void {
            std::cout << "testing exception......" << std::endl;
            
            catchSample();
            
            std::cout << "--------------------------------------\n";
        }
    }
    
    • 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

    执行结果如下:

    __cplusplus: 201703
    testing exception......
    +++Divide by zero+++
    --------------------------------------
    The end.
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.自定义异常类

    建议从标准的exception直接或间接地继承的方式实现自定义异常类。

    当某段代码抛出一个异常的时候,被抛出的值或者对象被复制,
    即通过使用复制构造函数从旧对象构造新对象;复制是必须的,
    因为原始对象在堆栈中的位置较高,因此可能在异常被捕获之前超出作用域(因此会被销毁,其所占的内存会被回收)。因此,如果编写的类的对象作为异常抛出,对象必须能被复制,这意味着如果动态分配了内存,必须编写析构函数、复制构造函数以及赋值运算符。P296

    提示:作为异常抛出的对象至少会按值复制一次,按引用捕获异常可以避免不必要的复制

    #pragma once
    
    #include <stdexcept>
    #include <string>
    #include <iostream>
    #include <fstream>
    
    namespace test_exception {
        class FileError : public std::runtime_error {
        public:    
            FileError(const std::string& filename): filename_(filename), msg_(""), std::runtime_error(""){}
        virtual const char* what() const noexcept override{return msg_.c_str();}
        protected:
            std::string filename_, msg_;
        };
        
        class FileOpenError : public FileError {
        public:
            FileOpenError(const std::string& filename): FileError(filename){
                msg_ = std::string("Unable to open ") + filename_;
            }
        };
    
        class FileReadError : public FileError {
        public:
            FileReadError(const std::string& filename, int linenumber): FileError(filename), linenum_(linenumber){
                msg_ = std::string("Exception occurs in file ") + filename_ + std::string(", line ") + std::to_string(linenum_);
            }
        protected:
            int linenum_;
        };
     
        auto main() -> void {
            std::cout << "testing exception......" << std::endl;
    
            std::string filename("../doc/daisy.txt");
            std::ifstream in_file(filename);
            try {
                if(in_file.fail()){
                    throw FileOpenError(filename);
                }
                
                std::string line;
                int linenum = 0;
                while(std::getline(in_file, line)){
                    linenum++;
                    std::cout << line << std::endl;
                }
                if(!in_file.eof()){
                    in_file.close();
                    throw FileReadError(filename, linenum);
                }
            }
            catch(const FileOpenError& e){
                std::cout << "Line: " << __LINE__ << ", " << e.what() << std::endl;
            }
            catch (const FileReadError& e){
                std::cout << "Line: " << __LINE__ << ", " << e.what() << std::endl;
            }
            catch(...){
                std::cout << "unknow error occurs." << std::endl;
            }
            in_file.close();
            
            std::cout << "--------------------------------------\n";
        }
    }
    
    • 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

    daisy.txt中的内容:

    D:/daisy/5547758_eea9edfd54_n.jpg
    D:/daisy/5673551_01d1ea993e_n.jpg
    D:/daisy/5673728_71b8cb57eb.jpg
    D:/daisy/5794835_d15905c7c8_n.jpg
    D:/daisy/5794839_200acd910c_n.jpg
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上述代码输出:

    __cplusplus: 201703
    testing exception......
    D:/daisy/5547758_eea9edfd54_n.jpg
    D:/daisy/5673551_01d1ea993e_n.jpg
    D:/daisy/5673728_71b8cb57eb.jpg
    D:/daisy/5794835_d15905c7c8_n.jpg
    D:/daisy/5794839_200acd910c_n.jpg
    --------------------------------------
    The end.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Reference

    1.Marc Gregoire, Nicholas A. Solter, Scott J. Kleper. C++高级编程(第2版). 清华大学出版社,2012

  • 相关阅读:
    excel clear format
    AspNetCore7.0源码解读之UseMiddleware
    解决wget下载过慢的问题
    Flink Yarn Per Job - 启动AM
    【Excel】单元格如何设置可选项、固定表头
    【花书笔记|PyTorch版】手动学深度学习7:模型选择、欠拟合和过拟合
    使用 Pycharm 调试远程代码
    Java面试八股文整理
    thinkphp5 注入 反序列化写文件 phar反序列化
    MySQL学习笔记9
  • 原文地址:https://blog.csdn.net/liugan528/article/details/125465386