[C++]——Exception handling

Foreword:

  • In this issue, I will explain to you the relevant knowledge about exception handling  !

Table of contents

(1) The traditional way of handling errors in C language

(2) C++ exception concept

(3) Abnormal use

1. Exception throwing and catching

1️⃣ Exception throwing and matching principles 

2️⃣ Exception stack expansion and matching principle in function call chain

2. Rethrow of exception 

3. Abnormal security

4. Exception specification

(4) Exception system of C++ standard library

(5) Abnormal advantages and disadvantages

Summarize


(1) The traditional way of handling errors in C language

First, let’s review the relevant ways of handling exceptions in C language:

  •  Termination procedures , such as assert, defect: difficult for users to accept. If a memory error occurs, the program will be terminated with a divide-by-zero error.

Here is a simple code description of using macros for exception handling:

#include <stdio.h>
#include <assert.h>


int divide(int num1, int num2) 
{
    assert(num2 != 0);  // 断言num2不等于0

    return num1 / num2;
}

int main() 
{
    int result = divide(10, 0);
    // 如果编译时定义了NDEBUG宏,assert会被禁用,否则会触发异常并终止程序执行

    return 0;
}

【explain】

1. In the above code, the function is used to implement the division operation of two integers. By using macros within functions, you can perform conditional judgments to ensure that the divisor is not zero. If it is zero, an exception will be triggered, terminating the execution of the program.

2. In the function, call the function and pass the divisor as 0. If the macro is not defined when compiling (i.e. debugging mode is not enabled), an exception is triggered and program execution is terminated. If a macro is defined, it will be disabled, no exception will be triggered, and the program will continue to execute subsequent code.

【Output display】


  • Return error code , defect: the programmer needs to find the corresponding error by himself. For example, the interface functions of many libraries in the system express errors by putting error codes in errno.

 The following is an example code that handles exceptions by returning an error code:

#include <stdio.h>
int divide(int num1, int num2, int* res)
{
    if (num2 == 0) 
    {
        return -1; // 返回错误码 -1 表示除数为零的异常情况
    }

    *res = num1 / num2;
    return 0; // 返回 0 表示成功
}

int main() 
{
    int num1 = 10, num2 = 0, res;
    int num = divide(num1, num2, &res);

    if (num != 0)
    {
        printf("Error: Divide by zero\n");
        // 处理错误的逻辑
    }
    else 
    {
        printf("Result: %d\n", res);
        // 处理正常情况的逻辑
    }

    return 0;
}

 【explain】

  1. In the function, call the function and pass the divisor by 0. The error code returned by the function is stored in a variable. By judging the value, you can determine whether it is a normal situation or an abnormal situation. If it is not equal to 0, it means that an exception has occurred and error handling can be performed according to the specific situation.
  2. If the error code returned is 0, it means that the division operation is successful. The calculation result can be obtained through the variable and the corresponding normal processing logic can be executed.

【Output display】

In practice, C language basically uses the method of returning error codes to handle errors. In some cases, the termination program is used to handle very serious errors.


(2) C++ exception concept
 

In C++, Exception is a mechanism used to handle errors during program runtime. Exceptions provide a way to break out of the normal program flow and pass error information to the appropriate handler for processing.

The following are some concepts about C++ exceptions:

  1. Exception throwing : When an exception occurs, you can use the throw statement to throw the exception. The throw statement usually contains an exception object, which can be a basic type, class object, or pointer;

  2. Exception catching : After an exception is thrown, the program can use try-catchstatement blocks to catch and handle the exception. The try block contains code where exceptions may occur, and catchblocks are used to catch and handle exceptions;

  3. Exception handler : The catch block is a block of code used to handle exceptions. In the catch block, corresponding processing logic can be executed based on the type of exception thrown. There can be multiple  catch blocks that match exception types one by one in order and execute matching processing logic.

If a block throws an exception, the method to catch the exception uses the try and catch keywords. Code that may throw
exceptions is placed in the try block. The code in the try block is called protection code.

  • The syntax for using try/catch statements is as follows:
     
try
{
    // 保护的标识代码
}catch( ExceptionName e1 )
{
    // catch 块
}catch( ExceptionName e2 )
{
    // catch 块
}catch( ExceptionName eN )
{
    // catch 块
}

【summary】

  1. By properly using the exception handling mechanism to capture and process errors in the program, the robustness and maintainability of the program can be increased;
  2. Proper exception handling can make the code clearer and more readable, and can better handle exception situations and improve the fault tolerance of the program.

(3) Abnormal use

1. Exception throwing and catching

In C++, exception throwing and matching principles follow the following basic principles:

1️⃣ Exception throwing and matching principles
 

  1. Exception thrown:

    • When an exception occurs in the program, you can use throwstatements to throw the exception.
    • throwStatements usually contain an exception object, which can be a primitive type, class object, or pointer.
  2. Exception match:

    • Exception matching refers to selecting catchthe block that handles the exception based on the type of exception thrown.
    • The C++ exception handling mechanism will match the types of blocks tryin the block catchin order to find the block that can handle the exception type catch.
  3. Exception type matching and inheritance relationship:

    • C++ allows exception types to form an inheritance relationship, that is, exception objects of derived classes can be catchcaptured by blocks of base classes.
    • If there is an inheritance relationship for the exception type, the block of the derived class catchshould be placed catchbefore the block of the base class; otherwise, the block of the derived class catchwill not be executed.
  4. Best matching exception handling:

    • The C++ exception handling mechanism will select the most matching catchblock to handle the thrown exception.
    • The best matching block is the block catchthat is able to handle the thrown exception type or its base class type , that is, the closest match for the exception type.catch
  5. Handling of exception not matching:

    • If tryan exception is thrown within a block and no matching catchblock is found to handle the exception, the exception is passed higher up the call stack.
    • If the exception is never handled by the matching catchblock, the program will eventually terminate execution and may output an exception message.

【Precautions】

  1. catchThe principle of exception throwing and matching is to choose to handle exceptions by matching blocks in order, so catchbe careful in the order arrangement of blocks;
  2. Usually, you should start with the specific exception type, and then match to the base class type to ensure that the exception can be handled correctly and execute the corresponding exception handling logic.

2️⃣ Exception stack expansion and matching principle in function call chain

In the function call chain, the exception stack expansion matching principle mainly specifies how to match exception types and select the correct exception handling code. When an exception occurs, the C++ runtime system checks the function calls in the call stack step by step, starting from the currently executing function , to find a block that matches the type of exception thrown catch.

The following are the principles for exception stack expansion and matching:

  • Check the current function's tryblock:

    • If the current function contains tryblocks, the runtime system looks for matching catchblocks.
  • Check the current function's catchblock :

    • If the current function contains a block that matches the type of exception thrown catch, then that catchblock will be executed.
    • If multiple matching catchblocks are found, the closest (nearest) catchblock will be selected to handle the exception.
  • If the current function has no matching catchblock :

    • The exception stack is expanded to the upper level calling function.
    • Repeat steps 1 and 2 until you find a matching catchblock or reach the top of the call stack.
  • catchIf no matching block is found in the entire call stack :

    • Execution of the program terminates and a standard library function is called terminate()to terminate the program.

 Important notes about exception stack unwinding and matching:

  • Exceptions are matched in the order in which the stack is expanded, not in the order in which exceptions are thrown.
  • An exception object of a derived class can be catchcaught by a block of the base class, so the block catchof the base class should be placed before the block of the derived class catch.
  • If an exception is thrown in a function without a matching catchblock handler, the exception is passed up the call stack until a matching catchblock is found or the program is terminated.
  • Exception stack unwinding will cross function and thread boundaries, so these matching principles also apply in multi-threaded programs.

 

For example the following example:

 

Next, let’s understand it in detail through the code:

double Division(int a, int b) 
{
    if (b == 0)
        throw "Division by zero condition!";
    else
        return ((double)a / (double)b);
}

void Func() 
{
    int len, time;
   cin >> len >> time;
   cout << Division(len, time) << endl;
}

int main() 
{
    try {
        Func();
    }
    catch (const char* errmsg) {
        cout << errmsg << endl;
    }
    catch (...) {
        cout << "unknown exception" << endl;
    }

    return 0;
}

Output display:

 

 【explain】

  1. Use the exception handling mechanism to catch and handle possible divide-by-zero exceptions. When division by zero occurs, a string constant exception is thrown and catch (const char* errmsg)caught by the block;
  2. If other types of exceptions occur, they are catch (...)caught by the block and the corresponding processing logic is executed.

 

2. Rethrow of exception
 

In C++, exception rethrowing allows a catchcaught exception to be handled inside a block and rethrown to allow higher-level exception handling code to further process the exception. throwExceptions can be rethrown using the statement.

Here is an example code that uses exception rethrowing:

double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Division by zero condition!";
	}
	return (double)a / (double)b;
}
void Func()
{
	// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
	// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
	// 重新抛出去。
	int* array = new int[10];
	try {
		int len, time;
		cin >> len >> time;
		cout << Division(len, time) << endl;
	}
	catch (...)
	{
		cout << "delete []" << array << endl;
		delete[] array;
		throw;
	}
	// ...
	cout << "delete []" << array << endl;
	delete[] array;
}
int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	return 0;
}

【explain】

  1. In Functhe function, if a divide-by-zero exception occurs, the exception will be caught and the deleted arrayinformation will be output. Then, arrayit is released (used delete[]) and throwthe exception is re-thrown using the statement. This way, the exception is passed to higher-level code.
  2. In mainthe function, the exception is caught by the outermost catchblock and the exception information is output.
  3. By Funcre-throwing the exception in the function and freeing it before and after catching the exception array, you can ensure that the associated resources have been released before the exception is passed to higher layers.

【summary】

By re-throwing the exception, the exception can be properly handled at the place where the exception is caught, and the same exception can be continued to be handled or other operations can be performed in higher-level code. This mechanism provides flexibility and upward propagation of errors. 


3. Abnormal security

  • The constructor completes the construction and initialization of the object . It is best not to throw an exception in the constructor , otherwise the object may be incomplete or not fully initialized
  • The destructor mainly cleans up resources . It is best not to throw an exception in the destructor, otherwise it may cause resource leaks (memory leaks, unclosed handles, etc.)
  • Exceptions in C++ often lead to resource leaks. For example, exceptions are thrown in new and delete, resulting in memory leaks, and exceptions are thrown between lock and unlock, resulting in deadlock. C++ often uses RAII to solve the above problems. About RAII we will explain in this section smart pointers

4. Exception specification

 In C++, an exception specification is a way of specifying in a function declaration the exceptions that a function may throw. An exception specification can be included as part of a function to identify the types of exceptions that the function may throw. Specifically, an exception specification specifies a list of exception types that a function can throw.

In C++98\03, exception specifications use throw()declarations. For example:

void foo() throw(int, std::exception);

【explain】

  1. The above code indicates that the function foomay throw exceptions of inttype and type;exception
  2. If the function throws another exception type not listed in the exception specification, the program calls unexpectedthe function, which by default causes terminatethe called program to terminate.

More examples are shown below: 

// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);

// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);

// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();

In C++11, a more flexible and safe exception handling mechanism was introduced, which is an alternative to exception specifications: exception descriptions . The exception description uses noexceptkeywords to specify whether the function is allowed to throw exceptions.

Functions using noexceptthe keyword can be called " noexceptfunctions" or "non-throwing exception functions". They have some important uses and advantages in:

  1. Optimizing performance : The compiler can noexceptmake some optimizations based on explicit promises to;
  2. Exception propagation : Helps avoid exceptions propagating to contexts where exceptions should not be handled;

Here are some noexceptexamples of usage:

void myFunction() noexcept {
  // 函数体,不会抛出异常
}

void anotherFunction() {
  // 函数体,可能会抛出异常
}

void myFunction2() noexcept(true) {
  // 与上面的 myFunction 等效,不会抛出异常
}

void myFunction3() noexcept(false) {
  // 与 anotherFunction 等效,可能会抛出异常
}

//不会抛出异常
thread (thread&& x) noexcept;

【Precautions】

  1. In C++11, noexceptkeywords can be used as part of the function type to indicate whether the function throws an exception;
  2. After C++17, noexceptfunction expressions are supported to dynamically decide whether to throw an exception. This makes exception specifications more flexible and dynamic in some specific situations.

(4) Exception system of C++ standard library

C++ provides a series of standard exceptions, defined in , that we can use in our programs. They are
organized in a parent-child class hierarchy as follows:
 

 

 

Note : In practice, we can inherit the exception class to implement our own exception class. But in practice, many companies define their own exception inheritance system like the above. Because the C++ standard library is not designed to be easy to use.
 

int main()
{
	try {
		vector<int> v(10, 5);
		// 这里如果系统内存不够也会抛异常
		v.reserve(1000000000);
		// 这里越界会抛异常
		v.at(10) = 100;
	}
	catch (const exception& e) // 这里捕获父类对象就可以
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "Unkown Exception" << endl;
	}
	return 0;
}

(5) Abnormal advantages and disadvantages

Advantages of C++ exceptions:

  • 1. The exception object is defined. Compared with the error code method, it can clearly and accurately display various error information, and even include stack call information , which can help better locate program bugs.
  • 2. The big problem with the traditional way of returning error codes is that in the function call chain, if the deep function returns an error, then we have to return the error layer by layer, and the outermost layer can get the error. See the details below explain.
// 1.下面这段伪代码我们可以看到ConnnectSql中出错了,先返回给ServerStart,
ServerStart再返回给main函数,main函数再针对问题处理具体的错误。
// 2.如果是异常体系,不管是ConnnectSql还是ServerStart及调用函数出错,都不用检查,因
为抛出的异常异常会直接跳到main函数中catch捕获的地方,main函数直接处理错误。
int ConnnectSql()
{
	// 用户名密码错误
	if (...)
		return 1;
	// 权限不足
	if (...)
		return 2;
}
int ServerStart() {
	if (int ret = ConnnectSql() < 0)
		return ret;
	int fd = socket()
		if(fd < 0)
		return errno;
}
int main()
{
	if (ServerStart() < 0)
		...
		return 0;
}
  • 3. Many third-party libraries contain exceptions, such as boost, gtest, gmock and other commonly used libraries, so we also need to use exceptions when using them.
  • 4. It is easier to handle some functions using exceptions. For example, the constructor does not return a value, so it is inconvenient to use error codes. For example, a function such as T& operator, if the pos is out of bounds, it can only use an exception or terminate the program processing, and there is no way to indicate an error through the return value.

Disadvantages of C++ exceptions:

  • 1. Exceptions will cause the execution flow of the program to jump wildly, and it is very chaotic, and it will jump randomly when an error is thrown during runtime. This will make it difficult for us to track and debug and analyze the program.
  • 2. Exceptions will have some performance overhead. Of course, with the fast speed of modern hardware, this impact is basically negligible.
  • 3. C++ does not have a garbage collection mechanism, and resources need to be managed by yourself. Exceptions can easily lead to abnormal security issues such as memory leaks and deadlocks. This requires the use of RAII to handle resource management issues. Learning costs are higher.
  • 4. The exception system of the C++ standard library is not well defined, causing everyone to define their own exception system, which is very confusing.
  • 5. Use exceptions as standardized as possible, otherwise the consequences will be disastrous. If exceptions are thrown at will, the users captured by the outer layer will be miserable. So there are two points in the exception specification: 1. The exception types thrown are inherited from a base class. 2. Whether a function throws an exception and what kind of exception it throws are standardized using func() throw();.

Summarize

The above is all the knowledge about exceptions in c++11. Next, a brief review of this article! ! !

  1. Generally speaking, the advantages of exceptions outweigh the disadvantages, so we still encourage the use of exceptions in engineering;
  2. In addition, OO languages ​​basically use exceptions to handle errors, which can also be seen as a general trend.
     

That's all for this article. Thank you for watching and supporting! ! !

Guess you like

Origin blog.csdn.net/m0_56069910/article/details/132540415