A deep understanding of the Exception Handling Mechanism in C ++
Exception Handling
Enhancing the error recovery capability is one of the most powerful ways to improve code robustness. The error handling method adopted in C language is considered to be tightly coupled, function users must write error handling code very close to function calls, which makes it clumsy and difficult to use. The exception handling mechanism is introduced in C ++, which is one of the main features of C ++ and a better way to consider and handle errors. Error handling can bring about the following advantages:
The compilation of error handling code is no longer tedious, and it is no longer mixed with normal code. Programmers only need to write the code they want to produce, then, write the handler that handles the error in a separate segment. If you call the same function multiple times, you only need to write an error handling code somewhere. Errors cannot be ignored. If a function must send an error message to the caller. It will throw an object describing this error. Traditional error handling and Exception Handling
Before discussing Exception Handling, let's talk about the traditional error handling methods in C language. The following three methods are listed:
If an error is returned in the function, the function sets a global error status flag. The signal is used as the signal processing system. In the function, the raise signal is used to set the signal processing function through signal. In this way, the coupling degree is very high, in addition, the signal values generated by different libraries may conflict. Use the non-local jump functions setjmp and longjmp in the Standard C library.
Here, setjmp and longjmp are used to demonstrate how to handle errors:
# Include
# Include
Jmp_buf static_buf; // It is used to store the processor context and jump to void do_jmp () {// do something. After simetime occurs a little error // After longjmp is called, static_buf processor information is loaded, then the second parameter is used as the return value of the setjmp function of the return vertex longjmp (static_buf, 10); // 10 is the error code, which can be processed accordingly} int main () {int ret = 0; // Save the processor information to static_buf, and return 0, which is equivalent to a mark, you can jump back to if (ret = setjmp (static_buf) = 0) {// the code to be executed do_jmp ();} else {// error if (ret = 10) std: cout <"a little error" <std: endl ;}}
The error handling method seems to have a low coupling degree. The normal code and the error handling code are separated, and the processed code is all merged. However, to process code based on this local jump method, there is a serious problem in C ++, that is, the object cannot be destructed, after a local jump, the destructor of the instantiated object will not be called. This will cause memory leakage. The following example shows this point.
# Include
# Include
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); // jump, after the jump, B cannot parse} int main () {if (setjmp (static_buf) = 0) {cout <"deal with some thing" <endl; test_base ();} else {cout <"catch a error" <endl ;}}
In the above Code, only the base class constructor will be called. When longjmp jumps, the B instance will not be destructed, however, the execution stream cannot be returned here, And the instance B will not be destructed. This is a problem that occurs when local redirection is used in C ++ to handle errors. Exceptions in C ++ do not exist. Next, let's take a look at how to define an exception and how to throw an exception and capture it.
Exception throw
class MyError { const char* const data;public: MyError(const char* const msg = 0):data(msg) { //idle }};void do_error() { throw MyError("something bad happend");}int main(){ do_error();}
In the above example, an exception class instance is thrown through throw. This exception class can be any custom class, and the input parameters Through instantiation can indicate the error message. In fact, an exception is a class with exception information. After an exception is thrown, it needs to be captured to be restored from the error. Next, let's take a look at how to capture an exception. In the preceding example, an exception is thrown to handle errors. The biggest difference is that, in the code block that is thrown by an exception, the object will be destructed, which is called a stack Reverse Solution.
Exception capture
The catch keyword is used in C ++ to capture exceptions. After an exception is caught, the exception can be processed. The statement block to be processed is called an exception processor. The following is an example of a simple exception Capture:
try{ //do something throw string("this is exception"); } catch(const string& e) { cout << "catch a exception " << e << endl; }
Catch is a bit like a function. It can have a parameter. The exception object thrown by throw will be passed as a parameter to match to catch and then enter the exception processor, the code above only shows how to throw an exception. When a try statement block is added, multiple exceptions may be thrown. How can this problem be solved, here, multiple catch statement blocks can be connected, which leads to another problem, that is, how to perform matching.
Exception matching
Exception matching I think is in line with the function parameter matching principle, but there are some differences. There is a type conversion during function matching, but the exception is not, the type conversion is not performed during the matching process. The following example shows the fact:
#include
using namespace std;int main(){ try{ throw 'a'; }catch(int a) { cout << "int" << endl; }catch(char c) { cout << "char" << endl; }}
The output result of the above Code is char, because the thrown exception type is char, so it matches the second exception processor. No type conversion occurred during the matching process. Convert char to int. Although the exception processor does not perform type conversion, the base class can match the derived class, which is valid in the function and exception matching. However, note that the catch parameter must be of the reference type or pointer type, otherwise
This causes the problem of cutting the derived class.
// Base 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, implements the class CBase: public Base {public: CBase (string msg): Base (msg) {} void what () {cout <"CBase:" <m_msg <endl ;}; int main () {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 ();}}
The above code can work normally. In fact, when we write our own exception handling functions, we also inherit standard exceptions to implement byte custom exceptions, however, if you change Base & to Base, the object will be cut. For example, the following code will cause compilation errors. Because CBase is cut, the test function in CBase cannot be called.
try { //do some thing throw CBase("I am a CBase exception"); }catch(Base e) { e.test(); }
So far, exception matching is clear. To sum up, the following rules are basically followed during exception matching:
In addition to strict type matching, exception matching also supports the following types.
Allow type conversion from non-constant to constant, that is, a non-constant type can be thrown, then, catch is used to capture the corresponding constant type version, which allows the type conversion from the derived class to the base class. This allows the array to be converted to an array pointer, and the function to be converted to a function pointer.
Assume that when I want to implement a generation of code, I want to catch exceptions of any type, currently, we can only write a lot of catch statements to catch all exceptions that may occur in the code to solve this problem. Obviously, this process is too cumbersome, fortunately, C ++ provides a mechanism to catch any exceptions and can use the syntax in the following code.
Catch (...) {// exception processor. Any exception can be caught here. The problem is that it cannot or exception information}
If you want to implement a function library, you capture some exceptions in your function library, but you only record logs and do not handle these exceptions, the Code called by the upper layer is used to handle exceptions. C ++ also supports such a scenario.
try{ throw Exception("I am a exception"); }catch(...) { //log the exception throw; }
You can add a throw to the catch Block to throw the caught exception again. in the exception throw section, I throw an exception in the code, but I didn't use any catch statements to catch the exception I throw, the following result is displayed when the above program is executed.
terminate called after throwing an instance of 'MyError'Aborted (core dumped)
Why is such a result ?, When we throw an exception, the exception will be thrown along with the function call relationship. It will not be stopped until it is captured. If it is not captured, the terminate function will be called, the above output is caused by the automatic call of the terminate function. 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 processed by the custom terminate function if it is not captured. the following is an example:
# Include
# Include
# Include
Using namespace std; class MyError {const char * const data; public: MyError (const char * const msg = 0): data (msg) {// idle }}; void do_error () {throw MyError ("something bad happend");} // custom terminate function. The function prototype must be consistent with void terminator () {cout <"I'll be back" <endl; exit (0) ;}int main () {// set the custom terminate, the returned result is the original terminate function pointer void (* old_terminate) () = set_terminate (terminator); do_error () ;}the above code will output I'll be back
So far, all the knowledge points I know about exception matching have been introduced. Next, let's look at the next topic and clear the resources in the exception.
Resource cleanup in exception
When talking about local redirection, it is said that local redirection will not call the object's destructor, which will cause memory leakage. Exceptions in C ++ will not cause this problem, in C ++, the defined objects are parsed through Stack Reverse decoding. However, if an exception occurs in the constructor, the allocated resources cannot be recycled, the following is an example of a constructor throwing an exception:
# Include
# Include
Using namespace std; class base {public: base () {cout <"I start to construct" <endl; if (count = 3) // throw an exception when constructing the fourth string ("I am a error"); count ++ ;}~ Base () {cout <"I will destruct" <endl;} private: static int count;}; int base: count = 0; int main () {try {base test [5];} catch (...) {cout <"catch some error" <endl ;}} the output result of the above Code is: I start to constructI will destruct I will destruct catch some error
An exception occurs in the constructor in the code above, causing the corresponding destructor to be not executed. Therefore, you should avoid throwing an exception in the constructor during actual programming. If there is no way to avoid this, the constructor must capture and process the constructor. the last knowledge point is the try statement block of the function. If the main function may throw an exception, how can this problem be captured ?, If the initialization list in the constructor may throw an exception, how can this problem be captured? The following two examples illustrate the usage of the try statement block:
# Include
Using namespace std; int main () try {throw "main";} catch (const char * msg) {cout <msg <endl; return 1 ;} the main function block can capture exceptions thrown by the main function. class Base {public: Base (int data, string str) try: m_int (data), m_string (str) // catch the exceptions that may occur in the initialization list {// some initialize opt} catch (const char * msg) {cout <"catch a exception" <msg <endl;} private: int m_int; string m_string ;}; int main () {Base base Base (1, "zhangyifei ");}
Many of the problems mentioned above are about the use of exceptions, how to define your own exceptions, whether exceptions should follow certain standards, where exceptions are used, and whether exceptions are safe, we will discuss it one by one.
Standard exception
The C ++ Standard Library provides us with a series of standard exceptions, which are derived from the exception class and mainly divided into two Derived classes: logic_error, the other class is runtime_error in the stdexcept header file. The former class describes logical errors in the program. For example, invalid parameters are passed, the latter refers to errors caused by unexpected events, such as hardware faults or memory depletion. Both of them provide a constructor with the parameter type std: string, in this way, the exception information can be saved and the exception information can be obtained through the what member function.
# Include
# Include
# Include
Using namespace std; class MyError: public runtime_error {public: MyError (const string & msg = ""): runtime_error (msg ){}}; // runtime_error logic_error both inherit from standard exceptions with string constructor // int main () {try {throw MyError ("my message");} catch (MyError & x) {cout <x. what () <endl ;}}
Exception specifications
If a project uses some third-party libraries, some functions in the third-party libraries may throw an exception, but we do not know, then C ++ provides a syntax, list the exceptions that a function may throw, so that we can refer to the exception description of the function when writing code, however, in C ++ 11, the exception specification description scheme has been canceled, so I don't want to introduce it too much. Let's look at its basic usage through an example, focus on the exception description solution provided in C ++ 11:
# Include
# Include
# Include
# Include
Using namespace std; class Up {}; class Fit {}; void g (); // exception Specification Description, the f function can only throw Up and Fit exceptions. void f (int I) throw (Up, Fit) {switch (I) {case 1: throw Up (); case 2: throw Fit ();} g ();} void g () {throw 47;} void my_ternminate () {cout <"I am a ternminate" <endl; exit (0);} void my_unexpected () {cout <"unexpected exception thrown" <endl; // throw Up (); throw 8; // if you continue to throw an exception in unexpected, the caught program continues to execute if the thrown exception is not in the specification. // 1. if the exception type description contains bad_exception, A bad_exception will be thrown. // 2. if bad_exception is not specified in the exception specification, the program will call the ternminate function // exit (0);} int main () {set_terminate (my_ternminate); set_unexpected (my_unexpected ); for (int I = 1; I <= 3; I ++) {// when an exception is thrown, it is not an exception in the exception Specification Description. // it will eventually call the unexpected function of the system. You can use set_unexpected to set your unexpected sweat function try {f (I );} catch (Up) {cout <"Up caught" <endl;} catch (Fit) {cout <"Fit caught" <endl;} catch (bad_exception) {cout <"bad exception" <endl ;}}}
The code above illustrates the basic syntax of the exception Specification Description, the role of the unexpected function, and how to customize your unexpected function. It also discusses the situation where the unexpected function continues to throw an exception, how to handle thrown exceptions. the exception specification is removed in C ++ 11. A notest function is introduced to indicate whether the function will throw an exception.
Void recoup (int) noexecpt (true); // recoup will not throw an exception void recoup (int) noexecpt (false); // recoup may throw an exception
Noexecpt is also provided to detect whether a function does not throw an exception.
Abnormal Security
Exception security I think is quite complicated. It is not just necessary to implement functions, but also to save functions that do not encounter inconsistency when an exception is thrown. here is an example. When we implement the stack, we often see that the examples in the book define a top function to obtain the top element of the stack, another pop function with the return value of void only pops up the top element of the stack. Why is there no pop function?
That is, the top element of the stack is displayed, and the top element of the stack can be obtained?
template
T stack
::pop(){ if(count == 0) throw logic_error("stack underflow"); else return data[--count];}
If the function throws an exception in the last row, the function does not return the elements of the rollback, but the count has been reduced by 1, therefore, the top element of the stack to be obtained by the function is lost. essentially, this function tries to do two things at a time. return Value, 2. changes the status of the stack. it is best to put these two independent actions into two independent functions and follow the cohesion design principle. Each function only does one thing. we
Another exception security issue is the writing of common value assignment operators. How to ensure the security of value assignment operations is abnormal.
class Bitmap {...};class Widget { ...private: Bitmap *pb;};Widget& Widget::operator=(const Widget& rhs){ delete pb; pb = new Bitmap(*rhs.pb); return *this;}
The above Code does not have the security of Self-assignment. If rhs is the object itself, it will lead to * rhs. pb points to a deleted object. so we are ready for improvement. join the same-sex test.
Widget & Widget: operator = (const Widget & rhs) {If (this = rhs) return * this; // same-sex test delete pb; pb = new Bitmap (* rhs. pb); return * this ;}
However, the above Code still does not comply with the exception security, because if an exception occurs when new Bitmap is executed after the delete pb execution is complete, it will eventually point to a deleted memory. now, you only need to change it a bit to ensure the exception security of the above Code.
Widget & Widget: operator = (const Widget & rhs) {If (this = rhs) return * this; // test Bitmap * pOrig = pb; pb = new Bitmap (* rhs. pb); // now, even if an exception occurs, the delete pOrig; return * this;} object pointed to by this will not be affected ;}
This example looks simple, but it is very useful. For the value assignment operator, reload is required in many cases.