• 三谈exception——错误处理


    1.内存分配错误

    如果无法分配内存,new以及new[]的默认行为都是抛出bad_alloc类型的异常,这个类型在<new>头文件中定义。应该捕获这些异常并且正确地处理异常。
    如果不喜欢异常,可以使用旧的C模式,无法分配内存的时候返回一个空指针。C++提供了new以及new[]nothrow版本,如果内存失败将返回nullptr而不是抛出异常,可以使用语法new(nothrow)而不是new做到这一点。

    #pragma once
    
    #include <iostream>
    #include <fstream>
    #include <stdexcept>
    #include <string>
    #include <stdio.h>
    
    namespace test_exception_errorprocess {
        auto generalNew() -> void {
            double *p = nullptr;
            try {
                p = new double[10LL*1024*1024*1024];
                printf("generalNew(), p = %p\n", p);
            }
            catch(std::bad_alloc& e) {
                std::cerr << "generalNew(), Fail to allocate memory." << e.what() << std::endl;
            }
            delete [] p;
        }
    	
        auto cStyleNew() -> void {
            double *p = nullptr;
            p = new(std::nothrow)double[10LL*1024*1024*1024];
            if(nullptr == p) {
                std::cerr << "cStyleNew(), Fail to allocate memory." << std::endl;
            }
            else {
                printf("cStyleNew(), p = %p\n", p);
            }
            delete[]p;
        }
        
        auto main() -> int {
    		std::cout << "testing exception_errorprocess..." << std::endl;
    
            generalNew();
            cStyleNew();
    
    		std::cout << "------------------------------" << std::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
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    以上程序输出:

    __cplusplus: 201703
    testing exception_errorprocess...
    generalNew(), Fail to allocate memory.std::bad_alloc
    cStyleNew(), Fail to allocate memory.
    ------------------------------
    The end.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.构造函数中的错误

    如果异常发生在构造函数中,则当前的对象可能只构造了一部分。有的成员已经初始化了,而另外一些成员在异常发生前也许还没有初始化。即使某个对象只构造了一部分,我们也要确保已构造的成员能被正确地销毁。

    如果异常离开了构造函数,将永远不会调用对象的析构函数。
    因此,应该在异常离开构造函数之前,仔细地清理所有资源,并释放构造函数中分配的所有内存。
    资源释放的问题与其它函数遇到的问题类似,但不同之处是离开其它函数时会自动调用析构函数处理内存分配以及释放资源。

    如果使用了继承会发生什么呢?父类的构造函数在子类的构造函数之前运行,如果子类构造函数抛出一个异常,如何释放父类构造函数分配的资源呢?实际上,C++保证会运行任何构建完整“子对象”的析构函数。因此,任何没有发生异常的构造函数所对应的析构函数都会运行。

    示例:

    #pragma once
    
    #include <iostream>
    #include <stdexcept>
    
    namespace test_exception_ctor {
        class Element {
        public:
            Element(){
                std::cout << "Element ctor called, idx: " << count_++ << std::endl;
            }
            ~Element(){
                std::cout << "Element dtor called, idx: " << --count_ << std::endl;
            }        
        protected:
            int val_;
            static int count_;
        };
        
        int Element::count_ = 0;
        
        class Matrix {
        public:
            Matrix(int w, int h);
            ~Matrix() {
                std::cout << "Calling Matrix dtor." << std::endl;
                
                for(int i = 0; i < height_; ++i) {
                    delete [] ele_[i];
                }
                delete [] ele_;
                ele_ = nullptr;
                
                std::cout << "Matrix dtor called." << std::endl;
            }
            
        private:
            int width_, height_;
            Element **ele_;
        };
        
        Matrix::Matrix(int w, int h): width_(w), height_(h), ele_(nullptr) {
            std::cout << "Calling Matrix ctor." << std::endl;
            
            ele_ = new Element*[height_];
            
            int i = 0;
            try {
                for(i = 0; i < height_; ++i) {
                    std::cout << "i = " << i << std::endl;
                    
                    if(i == 2) throw std::bad_alloc();  // 此句模拟i==2时内存申请失败,抛出异常
    
                    ele_[i] = new Element[width_];
                }
            }
            catch(...) {
                std::cerr << "exception occurs, idx: " << i << std::endl;
                
                // 释放已经申请的内存
                for(int j = 0; j < i; ++j) {
                    delete[] ele_[j];
                }
                delete[] ele_;
                ele_ = nullptr;
    
                // Translate any exception to std::bad_alloc
                throw std::bad_alloc();
            }
            
            std::cout << "Matrix ctor called." << std::endl;
        }
        
        auto main() -> int {
    		std::cout << "testing exception ctor..." << std::endl;
    
            try{
                Matrix amatrix(3,4);
            }
            catch(...) {
                std::cerr << "exception(s) occur(s)." << std::endl;
            }
            
    		std::cout << "------------------------------" << std::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
    • 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

    以上程序输出:

    __cplusplus: 201703
    testing exception ctor...
    Calling Matrix ctor.
    i = 0
    Element ctor called, idx: 0
    Element ctor called, idx: 1
    Element ctor called, idx: 2
    i = 1
    Element ctor called, idx: 3
    Element ctor called, idx: 4
    Element ctor called, idx: 5
    i = 2
    exception occurs, idx: 2
    Element dtor called, idx: 5
    Element dtor called, idx: 4
    Element dtor called, idx: 3
    Element dtor called, idx: 2
    Element dtor called, idx: 1
    Element dtor called, idx: 0
    exception(s) occur(s).
    ------------------------------
    The end.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    3.function-try-blocks(函数try语句块)

    构造函数在进入函数体之前首先执行初始值列表。因为在初始值列表抛出异常时构造函数体内的 try语句块还未生效,所以构造函数体内的catch语句无法处理构造函数初始值列表抛出的异常。
    要处理构造函数初始值抛出的异常,必须将构造函数写成函数try语句块(也称为函数测试块)的形式。
    function-try-blocks的用法(try放到初始化列表之前,catch语句放到构造函数体外部):

    MyClass::MyClass() try : <ctor-initializer> {
        /*...constructor body... */
    }
    catch(const std::exception& e) {
        /*...*/
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    function-try-blocks并不局限于构造函数,也可以用于普通函数。然而,对于普通函数而言没有理由使用function-try-blocks,因为其可以方便地转为函数体内部的try/catch块。相对于构造函数而言,对普通函数使用function-try-blocks的明显不同在于catch语句不需要抛出当前异常或者新的异常,C++运行时也不会自动重新抛出异常。

    使用function-try-blocks时,注意事项见如下章节3.1-3.6。

    3.1. function-try-blocks的catch语句将捕获任何异常,无论是构造函数体或ctor-initializer直接或间接抛出的异常。

    ctor-initializer抛出异常示例:

    #pragma once
    
    #include <iostream>
    #include <stdexcept>
    
    namespace test_exception {
        class Element {
        public:
            Element(int val):val_(val){
                std::cout << "Element ctor called." << std::endl;
                throw std::runtime_error("Element ctor exception.");
            }
            virtual ~Element() = default;
        protected:
            int val_;
        };
        
        class Entity {
        public:
            Entity(int val);
            ~Entity() noexcept(true){
                std::cout << "Entity dtor called.\n";
            }
            
        private:
            Element ele_;
        };
    
        Entity::Entity(int val) try : ele_(val){
            std::cout << "Entity ctor called.\n";
    		//throw std::runtime_error("Entity ctor exception.");
        }
        catch(const std::exception& e) {
            std::cout << "function-try-block caught: " << e.what() << std::endl;
            
            throw std::runtime_error("Exception occurs.");
        }
        
        auto main() -> int {
    		std::cout << "testing exception_function_try_blocks..." << std::endl;
    
            try{
                Entity entity(3);
            }
            catch(const std::exception& e) {
                std::cout << "main() caught: " << e.what() << std::endl;
            }
            
    		std::cout << "------------------------------" << std::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
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    输出:

    testing exception_function_try_blocks...
    Element ctor called.
    function-try-block caught: Element ctor exception.
    main() caught: Exception occurs.
    ------------------------------
    
    • 1
    • 2
    • 3
    • 4
    • 5

    构造函数体抛出异常示例:

    #pragma once
    
    #include <iostream>
    #include <stdexcept>
    
    namespace test_exception {
        class Element {
        public:
            Element(int val):val_(val){
                std::cout << "Element ctor called." << std::endl;
                //throw std::runtime_error("Element ctor exception.");
            }
            virtual ~Element() = default;
        protected:
            int val_;
        };
        
        class Entity {
        public:
            Entity(int val);
            ~Entity() noexcept(true){
                std::cout << "Entity dtor called.\n";
            }
            
        private:
            Element ele_;
        };
    
        Entity::Entity(int val) try : ele_(val){
            std::cout << "Entity ctor called.\n";
    		throw std::runtime_error("Entity ctor exception.");
        }
        catch(const std::exception& e) {
            std::cout << "function-try-block caught: " << e.what() << std::endl;
            
            throw std::runtime_error("Exception occurs.");
        }
        
        auto main() -> int {
    		std::cout << "testing exception_function_try_blocks..." << std::endl;
    
            try{
                Entity entity(3);
            }
            catch(const std::exception& e) {
                std::cout << "main() caught: " << e.what() << std::endl;
            }
            
    		std::cout << "------------------------------" << std::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
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    输出:

    testing exception_function_try_blocks...
    Element ctor called.
    Entity ctor called.
    function-try-block caught: Entity ctor exception.
    main() caught: Exception occurs.
    ------------------------------
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.2. function-try-blocks的catch语句必须重新抛出当前异常或者抛出一个新的异常。如果function-try-blocks的catch语句没有这么做,运行时将自动重新抛出当前异常。

    function-try-blocks的catch语句未重新抛出异常,运行时自动抛出当前异常示例:

    #pragma once
    
    #include <iostream>
    #include <stdexcept>
    
    namespace test_exception {
        class Element {
        public:
            Element(int val):val_(val){
                std::cout << "Element ctor called." << std::endl;
                throw std::runtime_error("Element ctor exception.");
            }
            virtual ~Element() = default;
        protected:
            int val_;
        };
        
        class Entity {
        public:
            Entity(int val);
            ~Entity() noexcept(true){
                std::cout << "Entity dtor called.\n";
            }
            
        private:
            Element ele_;
        };
    
        Entity::Entity(int val) try : ele_(val){
            std::cout << "Entity ctor called.\n";
    		//throw std::runtime_error("Entity ctor exception.");
        }
        catch(const std::exception& e) {
            std::cout << "function-try-block caught: " << e.what() << std::endl;
            
            //throw std::runtime_error("Exception occurs.");
        }
        
        auto main() -> int {
    		std::cout << "testing exception_function_try_blocks..." << std::endl;
    
            try{
                Entity entity(3);
            }
            catch(const std::exception& e) {
                std::cout << "main() caught: " << e.what() << std::endl;
            }
            
    		std::cout << "------------------------------" << std::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
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    输出:

    testing exception_function_try_blocks...
    Element ctor called.
    function-try-block caught: Element ctor exception.
    main() caught: Element ctor exception.
    ------------------------------
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.3. 当function-try-blocks的catch语句捕获了一个function-try-blocks内的异常时,构造函数已经构建的所有对象都会在执行catch语句之前销毁。

    示例:

    #pragma once
    
    #include <iostream>
    #include <stdexcept>
    
    namespace test_exception {
    	class Foo {
    	public:
    		Foo(){
    			std::cout << "Foo ctor called.\n";
    		}
    		~Foo(){
    			std::cout << "Foo dtor called.\n";
    		}		
    	};
    
    	class Bar {
    	public:
    		Bar(){
    			std::cout << "Bar ctor called.\n";
    		}
    		~Bar(){
    			std::cout << "Bar dtor called.\n";
    		}		
    	};
    	
        class Element {
        public:
            Element(int val): foo_(), val_(val){
                std::cout << "Element ctor called." << std::endl;
                //throw std::runtime_error("Element ctor exception.");
            }
            virtual ~Element() = default;
        protected:
            int val_;
    		Foo foo_;
        };
        
        class Entity {
        public:
            Entity(int val);
            ~Entity() noexcept(true){
                std::cout << "Entity dtor called.\n";
            }
            
        private:
    		// ele_在bar_之前,因此先构造ele_,再构造bar_
            Element ele_;
    		Bar bar_;
        };
    
        Entity::Entity(int val) try : bar_(), ele_(val){
            std::cout << "Entity ctor called.\n";
            throw std::runtime_error("Entity ctor exception.");
        }
        catch(const std::exception& e) {
    		std::cout << "Entering Entity's function-try-block......\n";
            std::cout << "function-try-block caught: " << e.what() << std::endl;
            
            throw std::runtime_error("Exception occurs.");
        }
        
        auto main() -> int {
    		std::cout << "testing exception_function_try_blocks..." << std::endl;
    
            try{
                Entity entity(3);
            }
            catch(const std::exception& e) {
                std::cout << "main() caught: " << e.what() << std::endl;
            }
            
    		std::cout << "------------------------------" << std::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
    • 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

    输出:

    testing exception_function_try_blocks...
    Foo ctor called.
    Element ctor called.
    Bar ctor called.
    Entity ctor called.
    Bar dtor called.
    Foo dtor called.
    Entering Entity's function-try-block......
    function-try-block caught: Entity ctor exception.
    main() caught: Exception occurs.
    ------------------------------
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.4. 对于function-try-blockcatch语句内部的对象,不应该访问其成员变量。

    以下示例说明catch语句内部的对象可以访问其成员变量。但建议不这么做。

    #pragma once
    
    #include <iostream>
    #include <stdexcept>
    
    namespace test_exception_function_try_blocks {
    	class Foo {
    	public:
    		Foo(){
    			std::cout << "Foo ctor called.\n";
    		}
    		virtual ~Foo(){
    			std::cout << "Foo dtor called.\n";
    		}		
    	};
    
    	class Bar {
    	public:
    		Bar(){
    			std::cout << "Bar ctor called.\n";
    		}
    		virtual ~Bar(){
    			std::cout << "Bar dtor called.\n";
    		}		
    	};
    	
        class Element {
        public:
            Element(int val): foo_(), val_(val){
                std::cout << "Element ctor called." << std::endl;
                throw std::runtime_error("Element ctor exception.");
            }
            virtual ~Element() = default;
            int getVal() const noexcept{
                return val_;
            }
        protected:
            int val_;
    		Foo foo_;
        };
        
        class Entity {
        public:
            Entity(int val);
            virtual ~Entity() noexcept(true){
                std::cout << "Entity dtor called.\n";
            }
            
        private:
    		// ele_在bar_之前,因此先构造ele_,再构造bar_
            Element ele_;
    		Bar bar_;
        };
    
        Entity::Entity(int val) try : bar_(), ele_(val){
            std::cout << "Entity ctor called.\n";
            //throw std::runtime_error("Entity ctor exception.");
        }
        catch(const std::exception& e) {
    		std::cout << "Entering Entity's function-try-block......\n";
            std::cout << "function-try-block caught: " << e.what() << std::endl;
            std::cout << "ele_.getVal() = " << ele_.getVal() << std::endl;   // 输出3
            throw std::runtime_error("Exception occurs.");
        }
        
        auto main() -> int {
    		std::cout << "testing exception_function_try_blocks..." << std::endl;
    
            try{
                Entity entity(3);
            }
            catch(const std::exception& e) {
                std::cout << "main() caught: " << e.what() << std::endl;
            }
            
    		std::cout << "------------------------------" << std::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
    • 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

    输出:

    __cplusplus: 199711
    testing exception_function_try_blocks...
    Foo ctor called.
    Element ctor called.
    Foo dtor called.
    Entering Entity's function-try-block......
    function-try-block caught: Element ctor exception.
    ele_.getVal() = 3
    main() caught: Exception occurs.
    ------------------------------
    The end.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.5. 对于对于function-try-block中的catch语句而言,其中包含的函数不能使用return关键字返回值。构造函数与此无关,因为构造函数没有返回值。

    #pragma once
    
    #include <iostream>
    #include <stdexcept>
    
    namespace test_exception_function_try_blocks {
    	class Foo {
    	public:
    		Foo(){
    			std::cout << "Foo ctor called.\n";
    		}
    		virtual ~Foo(){
    			std::cout << "Foo dtor called.\n";
    		}		
    	};
    
    	class Bar {
    	public:
    		Bar(){
    			std::cout << "Bar ctor called.\n";
    		}
    		virtual ~Bar(){
    			std::cout << "Bar dtor called.\n";
    		}		
    	};
    	
        class Element {
        public:
            Element(int val): foo_(), val_(val){
                std::cout << "Element ctor called." << std::endl;
                throw std::runtime_error("Element ctor exception.");
            }
            virtual ~Element() = default;
            int getVal() const noexcept{
                return val_;
            }
        protected:
            int val_;
    		Foo foo_;
        };
        
        class Entity {
        public:
            Entity(int val);
            virtual ~Entity() noexcept(true){
                std::cout << "Entity dtor called.\n";
            }
            
        private:
    		// ele_在bar_之前,因此先构造ele_,再构造bar_
            Element ele_;
    		Bar bar_;
        };
    
        Entity::Entity(int val) try : bar_(), ele_(val){
            std::cout << "Entity ctor called.\n";
            //throw std::runtime_error("Entity ctor exception.");
        }
        catch(const std::exception& e) {
    		std::cout << "Entering Entity's function-try-block......\n";
            std::cout << "function-try-block caught: " << e.what() << std::endl;
            return;  // [ERROR]Visual Studio 15 2017, error C2176: 不能在与构造函数关联的函数 try 块的处理程序中使用 return 语句
                     //        MinGW Makefiles, error: cannot return from a handler of a function-try-block of a constructor
            throw std::runtime_error("Exception occurs.");
        }
        
        auto main() -> int {
    		std::cout << "testing exception_function_try_blocks..." << std::endl;
    
            try{
                Entity entity(3);
            }
            catch(const std::exception& e) {
                std::cout << "main() caught: " << e.what() << std::endl;
            }
            
    		std::cout << "------------------------------" << std::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
    • 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

    此示例编译出错,详见注释。

    3.6 鉴于以上限制,构造函数的function-try-block只在少数情况下有用

    • 将ctor-initializer抛出的异常转换为其它异常;
    • 将信息记录到日志文件。

    4.析构函数中的错误

    必须在析构函数内部处理析构函数引起的所有错误,不应该让析构函数抛出任何异常。
    如果析构函数需要执行某个可能抛出异常的操作,则该操作应该被放置在一个try语句块中,并且在析构函数内部得到处理。
    在实际的编程中,因为析构函数仅仅是释放资源,所以它不大可能抛出异常。所有标准库类型都能确保它们的析构函数不会引发异常。

    在栈展开的过程中,运行类类型的局部对象的析构函数。因为这些析构函数是自动执行的,所以它们不应该抛出异常。一旦在栈展开的过程中析构函数抛出了异常,并且析构函数自身没能捕获到该异常,则程序将被终止。

    5.noexcept

    5.1 noexcept有两层含义

    1.当跟在函数参数列表后面时它是异常说明符;
    2.当作为noexcept异常说明的bool实参出现时,它是一个运算符。

    void recoup(int) noexcept;         // 不会抛出异常
    void recoup(int) noexcept(true);   // 不会抛出异常,与上句的等价写法
    void alloc(int);                   // 可能抛出异常
    void alloc(int) noexcept(false);   // 可能抛出异常,与上句的等价写法
    
    • 1
    • 2
    • 3
    • 4

    5.2 noexcept异常说明

    预先知道函数不会抛出异常的好处:
    1.有助于简化调用该函数的代码;
    2.如果编译器确认函数不会抛出异常,它就能执行某些特殊的优化操作,而这些优化操作并不适用于可能出错的代码。

    5.3 noexcept用于异常说明符时出现的位置

    1.noexcept要么出现在函数的所有声明语句和定义语句中,要么一次也不出现;
    2.noexcept应该在函数的尾置返回类型之前;
    3.放在构造函数的初始化列表之前;
    4.在成员函数中,noexcept说明符需要跟在const及引用限定符之后,而在finaloverride或虚函数的=0之前;
    5.在typedef或类型别名中则不能出现noexcept。

    5.4违反异常说明

    一般编译器并不会在编译时检查noexcept说明,如果一个函数在说明了noexcept的同时又含有throw语句或者调用了可能抛出异常的其它函数,编译器将顺利编译通过,并不会因为这种违反异常说明的情况而报错(不排除个别编译器会对这种用法提出警告)。
    // 尽管该函数明显违反了异常说明,但它仍然可以顺利编译通过

    void f() noexcept {     // 承诺不会抛出异常
    	throw exception();  // 违反了异常说明
    }
    
    • 1
    • 2
    • 3

    因此可能出现这样一种情况:尽管函数声明了不会抛出异常,但实际上还是抛出了,一旦一个noexcept函数抛出了异常,程序就会调用ternimate以确保遵守不在运行时抛出异常的承诺。
    示例:

    #include <iostream>
    #include <stdexcept>
    
    namespace test_exception {
        auto func1() noexcept(true) -> void {
    		std::cout << "func1()" << std::endl;
    		throw std::exception();   // [Visual Studio 15 2017] warning C4297: “test_exception::func1”: 假定函数不引发异常,但确实发生了
    		                          // [MinGW Makefiles] warning: 'throw' will always call 'terminate'
    	}
    	
        auto main() -> int {
    		std::cout << "testing exception..." << std::endl;
    		
    		func1();   // [Visual Studio 15 2017]运行时会崩溃,提示:abort()has been called
    
    		std::cout << "------------------------------" << std::endl;
    
            return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    5.5 什么时候使用noexcept?

    1.确认函数不会抛出异常;
    2.根本不知道如何处理异常。

    5.5 noexcept运算符是一个一元运算符

    noexcept运算符返回值是一个bool类型的右值常量表达式,用于表示给定的表达式是否会抛出异常。(和sizeof类似,noexcept也不会求其运算对象的值。)
    用法:noexcept(e),当e调用的所有函数都做了不抛出说明且e本身不含有throw语句时,上述表达式为true,否则noexcept(e)返回false
    例如:因为声明recoup时使用了noexcept说明符,所以noexcept(recout(i))返回true

    void f() noexcept(noexcept(g()));  // f和g的异常说明一致,如果函数g承诺了不会抛出异常,则f也不会抛出异常,
                                       // 如果g没有异常说明符,或g虽然有异常说明符但是运行抛出异常,则f也可能抛出异常
    
    • 1
    • 2

    示例:

    #include <iostream>
    #include <stdexcept>
    #include <typeinfo>
    
    namespace test_exception {
        auto func1() noexcept(true) -> void {
    		std::cout << "func1()" << std::endl;
    		throw std::exception();   // [Visual Studio 15 2017] warning C4297: “test_exception::func1”: 假定函数不引发异常,但确实发生了
    		                          // [MinGW Makefiles] warning: 'throw' will always call 'terminate'
    	}
    	
    	auto func2(int i) noexcept(true) -> void {
    		std::cout << "func2()" << std::endl;
    	}
    	
    	auto func3(int i) noexcept(false) -> void {
    		std::cout << "func3()" << std::endl;
    		throw std::exception();
    	}
    		
    	auto func4(int i) -> void {
    		std::cout << "func3()" << std::endl;
    	}
    	
    	void (*pf1)(int) noexcept(true);
    	void (*pf2)(int) noexcept(false);
    	
        auto main() -> int {
    		std::cout << "testing exception..." << std::endl;
    		
    		std::cout << "type(func1): " << typeid(func1).name() << std::endl;
    		std::cout << "type(func1()): " << typeid(func1()).name() << std::endl;
    
    		std::cout << "type(func2): " << typeid(func2).name() << std::endl;
    		std::cout << "type(func2()): " << typeid(func2(0)).name() << std::endl;
    
    		std::cout << "type(func3): " << typeid(func3).name() << std::endl;
    		std::cout << "type(func3()): " << typeid(func3(0)).name() << std::endl;
    
    		std::cout << "type(func4): " << typeid(func4).name() << std::endl;
    		std::cout << "type(func4()): " << typeid(func4(0)).name() << std::endl;
    
            // 写成函数调用的方式才能正确获取到函数是:noexcept(true)或noexcept(false)
    		std::cout << "noexcept(func1()): " << noexcept(func1()) << std::endl;   // 注意这里要写成函数调用的形式,不要写成noexcept(func1)
    		std::cout << "noexcept(func2(0)): " << noexcept(func2(0)) << std::endl;
    		std::cout << "noexcept(func3(0)): " << noexcept(func3(0)) << std::endl;
    		std::cout << "noexcept(func4(0)): " << noexcept(func4(0)) << std::endl;
    		std::cout << "3: " << noexcept(3) << std::endl;
    
            // 如果写成了函数名的形式则始终返回:true
    		std::cout << "noexcept(func1): " << noexcept(func1) << std::endl;
    		std::cout << "noexcept(func2): " << noexcept(func2) << std::endl;
    		std::cout << "noexcept(func3): " << noexcept(func3) << std::endl;
    		std::cout << "noexcept(func4): " << noexcept(func4) << std::endl;
    
    		
    		//func1();   // [Visual Studio 15 2017]运行时会崩溃,提示:abort()has been called
    
    		pf1 = func2;
    		// pf1 = func3; // error C2440: “=”: 无法从“void (__cdecl *)(int) noexcept(false)”转换为“void (__cdecl *)(int) noexcept”
    		
    		pf2 = func2;
    		pf2 = func3;
    		
    		std::cout << "------------------------------" << std::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
    • 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

    以上程序输出:

    testing exception...
    type(func1): void __cdecl(void) noexcept
    type(func1()): void
    type(func2): void __cdecl(int) noexcept
    type(func2()): void
    type(func3): void __cdecl(int)
    type(func3()): void
    type(func4): void __cdecl(int)
    type(func4()): void
    noexcept(func1()): 1
    noexcept(func2(0)): 1
    noexcept(func3(0)): 0
    noexcept(func4(0)): 0
    3: 1
    noexcept(func1): 1
    noexcept(func2): 1
    noexcept(func3): 1
    noexcept(func4): 1
    ------------------------------
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    5.6 异常说明与指针、虚函数

    尽管noexcept说明符不属于函数类型的一部分,但函数的异常说明仍然会影响函数的使用。
    如果为某个指针做了不抛出异常的声明,则该指针将只能指向不抛出异常的函数;如果显式或隐式地说明了指针可能抛出异常,则该指针可以指向任何函数,即使是承诺了不抛出异常的函数也可以。

    // recoup和pfunc1都承诺不会抛出异常
    void (*pfunc1)(int) noexcept = reoup;
    void (*pfunc2)(int) = recoup;  // 正确,recoup不会抛出异常,pfunc2可能抛出异常
    
    pfunc1 = alloc;    // 错误,alloc可能抛出异常,但pfunc1说明了其不会抛出异常
    pfunc2 = alloc;    // pfunc2和alloc都可能抛出异常
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果一个虚函数承诺了它不会抛出异常,则后续派生出来的虚函数也必须做出同样的承诺;如果基类的虚函数运行抛出异常,则派生类的对应函数既可以运行抛出异常,也可以不允许抛出异常。
    示例:

    #include <stdexcept>
    namespace test_exception {
        class Base{
    	public:
    		Base() = default;
    		virtual ~Base() = default;
    		
    		virtual void func1(int i) noexcept(true){}
    		virtual void func2(int i) noexcept(false){}
    	};
    	
    	class DerivedA: public Base {
    	public:		
    		DerivedA() = default;
    		virtual ~DerivedA() = default;
    		
    		virtual void func1(int i) noexcept(true) override{}  // [right]
    		virtual void func2(int i) noexcept(true) override{}  // [right]
    	};
    
    	class DerivedB: public Base {
    	public:	
    		DerivedB() = default;
    		virtual ~DerivedB() = default;
    		
    		virtual void func1(int i) noexcept(false) override{} // [wrong]
    		// [Visual Studio 15 2017]error C2694: “void test_exception::DerivedB::func1(int) noexcept(false)”: 重写虚函数的限制性异常规范比基类虚成员函数“void test_exception::Base::func1(int) noexcept”少
    		// [MinGW Makefiles] error: looser exception specification on overriding virtual function 'virtual void test_exception::DerivedB::func1(int) noexcept (false)'
    		virtual void func2(int i) noexcept(false) override{} // [right]
    	};
    	
        auto main() -> int {
    		std::cout << "testing exception..." << std::endl;
    		
    		std::cout << "------------------------------" << std::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
    • 37
    • 38
    • 39

    Reference

    1.Marc Gregoire, Nicholas A. Solter, Scott J. Kleper. C++高级编程(第2版). 清华大学出版社,2012.(P301-P308)
    2. Stanley B. Lippman, Josée Lajoie, Barbara E. Moo. C++ Primer 中文版(第5版).电子工业出版社,2013.(P690)

  • 相关阅读:
    [Linux打怪升级之路]-system V共享内存
    Java8新特性:stream()流的巧妙用法总结
    DDE图像增强
    07-SDRAM :FIFO控制模块
    wireshark提取视频数据之RTP包中提取H264和H265
    【论文阅读 09】融合门控自注意力机制的生成对抗网络视频异常检测
    django配置前端文件放置的位置
    传输层协议(TCP/UDP协议)
    JVM阶段(6)-方法区回收
    深入理解HTTP的基础知识:请求-响应过程解析
  • 原文地址:https://blog.csdn.net/liugan528/article/details/125493862