C++ exception handling mechanism

1. C language for error handling

    First an example

#include <iostream>
#include <setjmp.h>
jmp_buf static_buf; //Used to store the processor context for jumping
void do_jmp()
{
    //do something,simetime occurs a little error
    //After calling longjmp, the processor information of static_buf will be loaded, and then the second parameter is used as the return value of the setjmp function of the return point
    longjmp(static_buf,10);//10 is the error code, and the corresponding processing is performed according to this error code
}
intmain()
{
    int ret = 0;
    //Save the processor information to static_buf and return 0, which is equivalent to making a mark here, and you can jump to it later
    if((ret = setjmp(static_buf)) == 0) {
        // code to execute
        do_jmp();
    } else { //There was an error
        if (ret == 10)
            std::cout << "a little error" << std::endl;
    }
}

    The error handling method does not seem to have a very high degree of coupling. The normal code and the error handling code are separated, and the processing code is gathered together. However, there is a serious problem in C++ to process code based on this local jump, that is, the object cannot be destructed, and the destructor of the instantiated object will not be actively called after the local jump. This will cause a memory leak problem. The following example illustrates this well:

#include <iostream>
#include <csetjmp>
using namespace std;
class base {
    public:
        base() {
            cout << "base construct func call" << endl;
        }
        ~base() {
            cout << "~base destruct func call" << endl;
        }
};
 
jmp_buf static_buf;
 
void test_base() {
    base b;
    //do something
    longjmp(static_buf,47);//After jumping, you will find that b cannot be destructed
}
 
int main() {
    if(setjmp(static_buf) == 0) {
        cout << "deal with some thing" << endl;
        test_base();
    } else {
        cout << "catch a error" << endl;
    }
}

2. C++ language for error handling

2.1 The matching of exceptions conforms to the parameter principle

The C++ exception handling mechanism is a very powerful and flexible tool for effectively handling runtime errors, providing more flexibility, safety, and robustness, overcoming the problems of traditional methods. The throwing and handling of exceptions mainly use the following three keywords: try, throw, catch.

The try-catch statement has the following form:
try
{  
        contains statements that may throw exceptions;  
}  
catch(typename[parametername]) // catch a specific type of exception  
{  
 
}  
catch(typename[parametername]) // catch a specific type of exception  
{  
 
}  
catch(...) // three dots means to catch all types of exceptions  
{  
}
Let's look at the problem of type matching:
#include <iostream>
using namespace std;
intmain()
{
    try{
        throw 'a';
    }catch(int a) {
        cout << "int" << endl;
    }catch(char c) {
        cout << "char" << endl;
    }
}

    The output of the above code is char, because the thrown exception type is char, so it matches the second exception handler. It can be found that no type conversion occurs during the matching process. Convert char to int.

    However, the base class can match the derived class, which is valid in both function and exception matching, but it should be noted that the formal parameter of the catch needs to be a reference type or a pointer type, otherwise it will cause the problem of cutting the derived class.

/base class
class Base{
    public:
        Base(string msg):m_msg(msg)
        {
        }
        virtual void what(){
            cout << m_msg << endl;
        }
    void test()
    {
        cout << "I am a CBase" << endl;
    }
    protected:
        string m_msg;
};
//Derived class, reimplemented virtual function
class CBase : public Base
{
    public:
        CBase(string msg):Base(msg)
        {
 
        }
        void what()
        {
           cout << "CBase:" << m_msg << endl;
        }
};
intmain()
{
    try {
        //do some thing
    // throw the derived class object
        throw CBase("I am a CBase exception");
 
    }catch(Base& e) { //Use the base class to receive
        e.what();
    }
}

Matching principle:

  • Allow non-const to constant type conversion, that is to say, you can throw a non-const type, and then use catch to capture the corresponding constant type version
  • Allow type conversion from derived class to base class
  • Allows arrays to be converted to array pointers and functions to function pointers

2.2 Rethrow

    Why rethrow the exception? In actual projects, we can capture and reinterpret exceptions thrown in third-party libraries (unifying exception types to facilitate code problem location), and then throw them again. The format is as follows:
try{
    throw Exception("I am a exception");    
}catch(...) {
    //log the exception
    throw;
}

    If we throw an exception in the code, but I do not use any catch statement to catch the exception, executing the above program will produce the following result:

terminate called after throwing an instance of 'MyError'
Aborted (core dumped)

    Why does such a result occur? When we throw an exception, the exception will be thrown up level by level with the function call relationship, and will not stop until it is caught. If it is not caught in the end, it will cause the terminate function to be called. The above output is caused by the automatic call to the terminate function. In order to ensure greater flexibility, C++ provides the set_terminate function that can be used to set its own terminate function. After the setting is complete, the thrown exception will be handled by the custom terminate function if it is not caught.

1. Define a function with no return value and no parameters (the function type is void(*)() type)

    a) cannot throw exceptions

    b) the current program must be ended somehow (abort/exit/...)

2. Call set_terminate to register the 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;
    }
};

intmain()
{
    set_terminate(my_terminate);

    static Test t;

    throw 1;

    return 0;
}
Note: In the actual programming process, you should avoid throwing exceptions in the constructor. If there is no way to avoid it, you must capture it in the constructor to handle it.

2.3 Exception caught by the initialization list in the main function and the constructor

#include <iostream>
using namespace std;
int main() try {
    throw "main";
} catch(const char* msg) {
    cout << msg << endl;
    return 1;
}
//The main function statement block can catch the exception thrown in the main function.
class Base
{
    public:
        Base(int data,string str)try:m_int(data),m_string(str)//The exceptions that may appear in the initialization list will also be caught
       {
            // some initialize opt
       }catch(const char* msg) {
 
            cout << "catch a exception" << msg << endl;
       }
 
    private:
        int m_int;
        string m_string;
};
intmain()
{
    Base base(1,"zhangyifei");
}

2.4 Common exception handling

dynamic memory allocation error

    Allocating dynamic memory uses the new and new[] operators. If they fail to allocate memory, they will throw bad_alloc exceptions in the new header file, so our code should catch these exceptions. Common code forms are as follows:

try {  
    //other code  
    ptr = new int[num_max];  
    //other code  
} catch(bad_alloc &e) {  
    //The common processing method here is: first release the allocated memory, then end the program, or print an error message and continue execution  
}  
    It can be handled in a similar way to C, but the nothrow version to be used at this time uses the "new (nothrow)" form to allocate memory. At this time, if the allocation is unsuccessful, a NULL pointer is returned instead of throwing a bad_alloc exception. It is also possible to customize the memory allocation failure behavior. C++ allows specifying a new handler (newhandler) callback function. There is no new handler by default. If we set a new handler, when new and new[] fail to allocate memory, the new handler we set will be called instead of throwing an exception directly. The callback function is set by the set_new_handler function. The function being called back is required to have no return value and no formal parameters.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325981798&siteId=291194637