Exception classes in c + + language

Reprint: https://blog.51cto.com/11134889/2068877
1. C language exception handling
1.1. The concept of anomaly
Exceptions: Exceptions may occur during the execution of a program (which is a predictable branch of execution when the program is running). For example, when the program is divided by 0, the external files that need to be opened do not exist, and when the array accesses beyond the bounds.
Bug: A bug is a bug in a program that can't be expected to run, such as a wild pointer, unreleased heap memory after the end, selection sort can't handle arrays of length 0.

1.2, C language exception handling
1.2.1 Classical Method:
If (to determine whether an exception has occurred)
{
// Normal code logic
}
else
{
// Exception code logic
}

#include <iostream>
#include <string>

using namespace std;

double divide(double a, double b, int* valid)
{
    const double delta = 0.000000000000001;  //Normally do not compare floating-point numbers with zero directly.
    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;
}

1.2.2.setjmp() and longjmp()
int setjmp(jmp_buf env)
Save the current context in the jmp_buf structure
void longjmp(jmp_buf env, int val)
Restore the context saved by setjmp() from the jmp_buf structure
Finally, it returns from the setjmp function call point with the return value of val.

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

using namespace std;
/**Defects: The introduction of setjmp() and longjmp() inevitably involves global variables, and violent jumps lead to reduced code readability***/

static jmp_buf env;     //Define global variables

double divide(double a, double b)
{
    const double delta = 0.000000000000001;     //Normally do not compare floating-point numbers with zero directly.
    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;
}

Defects: The introduction of setjmp() and longjmp() inevitably involves global variables, and violent jumps lead to reduced code readability

2. Exception handling in C++ (Part I)
2.1 try,catch,throw
C++ has built-in syntax elements try, catch, throw for exception handling
The try statement handles normal code logic
The catch statement handles exceptions
C++ throws exception information through throw statement
Exceptions in try statements are handled by corresponding catch statements
When a function throws an exception at run time to the place where the function is called (inside the try statement), the try statement handles the exception to the corresponding catch statement.

#include <iostream>
#include <string>

using namespace std;

/**When a function throws an exception at run time to the place where the function is called (inside the try statement), the try statement handles the exception to the corresponding catch statement.**/
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.2 C++ exception handling analysis
- The exception thrown by throw must be handled by catch
The current function can handle exceptions and the program continues to execute
If the current function cannot handle exceptions, the function stops executing and returns (the unprocessed exceptions propagate up the function call stack until they are processed, otherwise the program will stop executing)

2.3 Customize specific exception types
Different exceptions are handled by different catch statements
- catch(... ) Used to handle all types of exceptions (can only be placed at the end)

#include <iostream>
#include <string>

using namespace std;

/**When an exception is thrown, the top-down matches strictly the type of each catch statement processing without any type of conversion.**/
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;
}

Note: Any exception can only be caught once. When an exception is thrown, it will be caught top-down and strictly match the type handled by each catch statement without any type of conversion.

3. Exception Handling in C++ (Part 2)
3.1. catch Reinterpretation
Anomalies captured in catch can be reinterpreted and thrown. Anomalies thrown by catch require an outer try. Catch... capture
Why throw the exception again?
In practical engineering, we can catch and reinterpret exceptions thrown by third-party libraries (unify the types of exceptions, facilitate the location of code problems), and then throw them out.

#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;
    }
}

/*
    Hypothesis: Current functions are functions in third-party libraries, so we can't modify the source code

  

  Function name: void func(int i)
    Type of exception thrown: int
                        -1 ==> Parametric anomaly
                        -2 ==> Abnormal operation
                        -3 ==> Timeout anomaly

*/

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)  //Call third-party library functions, catch and reinterpret exceptions, and then throw
{
    try
    {
        func(i);
    }
    catch(int i)
    {
        switch(i)
        {
            case -1:
                throw "Invalid Parameter";      //Capture exceptions and reinterpret and throw them
                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;
}

Be careful:
(1) The type of exception can be a custom class type, and the matching of class type exception is still top-down and strict.
(2) The principle of assignment compatibility is still applicable in exception matching, generally speaking
- catch that matches subclass exceptions is placed at the top
- catch that matches parent exceptions is placed at the bottom

#include <iostream>
#include <string>

using namespace std;

class Base
{
};
//The type of exception can be a custom class type
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;
    }
};

/*
    Hypothesis: Functions in current functional third-party libraries, so we can't modify the source code

    Function name: void func(int i)
    Type of exception thrown: int
                        -1 ==> Parametric anomaly
                        -2 ==> Abnormal operation
                        -3 ==> Timeout anomaly
*/
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);
    }

    //References are recommended as parameters when defining catch statement blocks (prevent copy construction)
    // The assignment compatibility principle still applies in exception matching, generally speaking
    catch(const Exception& e)   // catch that matches subclass exceptions is placed at the top
    {
        cout << "Exception Info: " << endl;
        cout << "   ID: " << e.id() << endl;
        cout << "   Description: " << e.description() << endl;
    }
    catch(const Base& e)        // catch matching parent exceptions is placed at the bottom
    {
        cout << "catch(const Base& e)" << endl;
    }

    return 0;
}

3.2 Engineering Recommendations:
In engineering, a series of exception classes are defined, each of which represents an exception type that may occur in the project.
Different exception classes may need to be reinterpreted in code reuse
References are recommended as parameters when defining catch statement blocks (prevent copy construction)

3.3. Abnormal Class Family of Standard Library
(1) The C++ standard library provides a family of practical exception classes, which are derived from exception classes. There are two main branches.
- logic_error (often used in programs to avoid logical errors)
- runtime_error (often used as an unavoidable malignant error in programs)
Exceptions in the standard library:
C++ Language (13) - C++ Exception Handling

4. Deep analysis of exception handling
Question 1: The main function throws an exception
What happens when an exception runs out of the main function? Where will the exception go if it is not handled?

#include <iostream>

using namespace std;

class Test 
{
public:
    Test() 
    {
        cout << "Test()"; 
        cout << endl;
    }

    ~Test() 
    {
        cout << "~Test()"; 
        cout << endl;
    }
};

int main()
{
    static Test t;

    throw 1;

    return 0;
}

The experimental results show that if the exception is not handled, the program will end abnormally and print the exception statement.
Where are exceptional statements printed?
If the exception cannot be handled, the terminate() function is automatically called, which is used to terminate the exception, and the library function abort() function terminator is called in the terminate() function (abort function causes the program to execute an exception and exit immediately).
C++ grammar supports the implementation of custom terminate() functions:
(1) Define a function with no return value and no parameters (function type is void (*)())
a) Can't throw an exception
b) The current procedure must be terminated in some way (abort/exit/...)
(2) Call set_terminate to register a custom terminate() function
a) The return value is the default terminate function entry address.

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

using namespace std;

void my_terminate()
{
    cout << "void my_terminate()" << endl;
    exit(1);
}

class Test 
{
public:
    Test() 
    {
        cout << "Test()"; 
        cout << endl;
    }

    ~Test() 
    {
        cout << "~Test()"; 
        cout << endl;
    }
};

int main()
{
    set_terminate(my_terminate);

    static Test t;

    throw 1;

    return 0;
}

Question 2: Disjunctor throws an exception
What happens when an exception is thrown in a destructor?

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

using namespace std;

void my_terminate()
{
    cout << "void my_terminate()" << endl;
    // exit(1);
    abort();    // The terminate() function in C++ standard library calls abort to terminate the program directly. It will not call the destructor anymore, so as to prevent the exception from throwing out in the destructor.
}

class Test 
{
public:
    Test() 
    {
        cout << "Test()"; 
        cout << endl;
    }

    ~Test() 
    {
        cout << "~Test()"; 
        cout << endl;

        throw 2;    // terminate function is the last chance for the whole program to release resources
                    // An exception cannot be thrown in a destructor, which can cause terminate functions to be called many times, resulting in repeated release of resources.

    }
};

int main()
{
    set_terminate(my_terminate);

    static Test t;

    throw 1;

    return 0;
}

Experiments show that terminate() function is called when an exception is thrown in a destructor in a stable environment like Linux. It seems that there is no problem, but for some embedded systems, it may lead to system instability.
Conclusion:
The terminate function is the last chance for the entire program to release resources.
An exception cannot be thrown in a destructor, which will cause terminate functions to be called many times, resulting in repeated release of resources.
The terminate() function in C++ standard library calls abort function. It terminates the program directly and will not call the destructor any more, so as to prevent the exception from throwing out in the destructor.

5. Specification of Function Exceptions
5.1 How to determine whether a function will throw exceptions and what exceptions will be thrown?
C++ grammar provides exceptions thrown by declaring functions, which are written as modifiers to function declarations.
// Any exception may be thrown
void fun(void) ;
// Only int-type anomalies can be thrown
void fun(void) throw(int);
// Can't throw an exception
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;
}

Significance of exception specification:
- Prompt function calls that must be prepared for exception handling
- The maintainer of the prompt function should not throw other exceptions
- The exception specification is part of the function interface.

5.2 What happens if the exception thrown is not in the declaration list?
The exception thrown by the function is not in the specification, and the global function unexpected() is called
The default unexpected () function calls the global terminate() function
You can customize unexpected() functions
- Function type void (*) (void)
- Can throw an exception again
(a) When the next thrown exception meets the exception specification function of the trigger function, the program resumes execution.
b) Otherwise, the terminate() function is called to terminate the program.
Call the set_unexpected() function to set the custom exception function and return the default unexpected function entry address.
Note that not all C++ compilers support this standard behavior, where vs does not support (exceptions thrown are handled directly, although they are not in the exception specification declaration).

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

/**
The exception thrown by the function is not in the specification, and the global function unexpected() is called
    The default unexpected () function calls the global terminate() function
    You can customize unexpected() functions
        --Function type void (*) (void)
        --Can throw an exception again
            a)When the next thrown exception meets the exception specification function of trigger function, the program resumes execution.
            b)Otherwise, the terminate() function is called to terminate the program.
        --Call the set_unexpected() function to set the custom exception function, and return the default unexpected function entry address.
    Note that not all C++ compilers support this standard behavior, where vs does not support (exceptions thrown are handled directly, although they are not in the exception specification declaration).
**/

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;
}

6.try... Other Writings of catch
1. try... catch is used to separate normal logic code and exception handling code. It can directly separate function implementation into two parts.
2. Function declaration and definition can directly specify the type of exception thrown. Exception declaration as part of function can improve the readability of code.

#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;
}

Keywords: C Linux

Added by aaron_karp on Tue, 30 Jul 2019 18:34:47 +0300