c/c++ development, unavoidable custom class type (Part 8). Properly handle exceptions for classes

Table of contents

1. Brief description of the exception

        1.1 What is an exception

        1.2 Exception handling concept

2. Exception handling

        2.1 try...catch exception handling syntax

        2.2 Dynamic exception description-throw

        2.3 Standard exception system

        2.4 Try ... catch throws exception object processing

        2.5 Exception capture processing level

        2.6 Throwing Object Method

        2.7 The use of jump syntax is strictly prohibited in try blocks and processing blocks

        2.8 Exception capture and exception handling separation

        2.9 Array and function type transfer

        2.10 Exceptions and pointers

        2.11 Custom exceptions are best inherited from standard exception classes        

        2.12 "Stack unwinding" of exception handling

        2.13 Class construction, destructor and exception handling

        2.14 Rethrowing exceptions

        2.15 Managing Resource Allocation with Classes

3. Exception handling auxiliary keywords

        3.1 noexcept specifier

        3.2 Dynamic exception description-throw

        3.3 throw and potential exceptions

         3.4 throw and member functions

        3.5 Exception description throw and virtual function

        3.6 std::terminate

        3.7 std::unexpected

Fourth, about the assertion - macro assert

        4.1 Macro assert definition

        4.2 The macro assert is used in conjunction with the NDEBUG macro

        4.3 Pay attention to the exception of the macro assert replacement expression

        4.4 Compile assertion - static_assert

5. Exception handling suggestions

6. Demo source code


1. Brief description of the exception

        1.1 What is an exception

        Abnormal, in a word, is a program error, which exists outside the normal function of the program . If an exception occurs in the program, but the exception is not caught, the program will be terminated .

        C/C++ provides a lot of freedom and flexibility in memory management, but the security of applications developed in this language largely depends on the programmer's testing and detection links. Class designers need to pay special attention to the ability to control exceptions when designing custom class types.

        Of course, in terms of class design, in addition to memory leaks, which are often criticized by C/C++, other exception captures also need to be handled with care, such as logic exceptions: out-of-bounds exceptions, invalid parameters, length exceptions, etc., and runtime exceptions: System exceptions, format exceptions, range exceptions, etc.

         When writing code, we often write abnormal code unconsciously without knowing it, such as: using unhandled or raw pointers, resource leaks, writing non-compliant constructors and destructors , The program that has been running for a long time suddenly crashes. Executor and library routines have increased in size while running slower.

        If an exception occurs, it must be dealt with, otherwise the program exits abnormally. C/C++ exception handling provides a means by which a program can transfer control flow and information from a point of execution to handling code associated with a point previously traversed by execution (in other words, exception handling moves control up the call stack Transfer, this is "stack unwinding", which will be described in detail in later chapters ). C/C++ provides a variety of exception handling methods: throw expression, dynamic_cast, typeid, new expression, allocation function, and any standard library function designed to throw a specific exception to indicate a specific error state (such as std::vector ::at, std::string::substr, etc.) can all throw exceptions.

        To catch an exception, the throw expression must be within a try block or a function called within the try block, and must have a catch clause that matches the type of the exception object.

        When declaring a function (non-member function, member function), the following specifications can be provided to limit the types of exceptions that the function can throw:

  • ◦Dynamic exception description (before C++17) (i.e. throw (type identification list (optional)), see the following chapters)
  • ◦noexcept description (from C++11) ( for details, refer to a blog post in this column (Part 7), which will not be expanded in this article ). noexcept is an improved version of throw(), which was deprecated in C++11.

        Errors that occur during exception handling are handled by std::terminate and std::unexpected (before C++17) ( the two keywords will be explained in later chapters ).

        1.2 Exception handling concept

        [1] Error handling

        The throwing of exceptions is used to signal errors from functions, where "error" is usually limited to the following:

  1. Failure to satisfy postconditions, such as failure to produce a valid return value object;
  2. A precondition of another function that must be called cannot be satisfied;
  3. (For non-private member functions) Class invariants cannot (no longer) be established.

        This means that constructors and most operators should report program errors by throwing exceptions. In addition, so-called wide contract functions use exceptions to indicate illegal input, for example, std::string::at has no preconditions, but it throws an exception to indicate that the subscript is out of bounds.

        [2] Abnormal security

        After a function reports an error condition, additional guarantees can be provided to safeguard the state of the program. The following are four widely recognized exception assurance levels, each a strict superset of the other:

  1. No throw (nothrow) (or no fail) exception guarantee --- the function will never throw an exception. Destructors and other functions that may be called during a stack unwind are expected not to throw (and otherwise report or suppress errors). Destructors default to noexcept. (since C++11) swap functions, move constructors, and other functions used to provide strong exception guarantees are expected to never fail (the function always succeeds).
  2. Strong exception guarantee --- if a function throws an exception, the state of the program is rolled back to exactly what it was before the function was called. (e.g. std::vector::push_back)
  3. Basic Exception Guarantee --- If a function throws an exception, then the program is in some valid state. No resources are leaked, and all object invariants remain intact.
  4. No Exception Guarantee - If a function throws an exception, then the program may not be in a valid state: a resource leak, memory corruption, or other error that destroys an invariant may have occurred.

        In addition, generic components can also provide exception neutral (exception neutral) guarantee: if thrown from a template parameter (such as from std::sort's Compare function object, or from the constructor of T in std::make_shared) exception, it is propagated unmodified to the caller.

        [3] Exception object

        While arbitrary complete types and cv pointers to void can be thrown as exception objects, all standard library functions throw anonymous temporary objects by value, and the types of these objects are derived (directly or indirectly) from std::exception. User-defined exceptions generally follow this pattern. To avoid unnecessary copying of exception objects and object slicing, catch clauses are best practiced to capture by reference.

2. Exception handling

        2.1 try...catch exception handling syntax

        Exceptions cannot be ignored. When necessary, the c/c++ standard operation allows developers to call try...catch to catch exceptions, and then pass the exception information to an object and throw the object. Through this object, it is possible to determine what error occurred and how to handle the exception.

        try...catch associates one or more exception handling blocks (catch clauses) with compound statements, and its syntax is implemented as follows:

try 复合语句 处理块序列   
//其中处理块序列是一或多个处理块的序列,它有下列语法:
catch (属性(可选) 类型说明符序列声明符) 复合语句 
// (1) 声明一个具名形参的catch子句:try { /* */ } catch (const std::exception& e) { /* */ }
catch (属性(可选) 类型说明符序列抽象声明符(可选) ) 复合语句   
//(2)声明一个无名形参的catch子句:try { /* */ } catch (const std::exception&) { /* */ }
catch ( ... ) 复合语句   
//(3)catch-all处理块,可被任何异常激活:try { /* */ } catch (...) { /* */ }
/*参数说明
*复合语句         - 花括号环绕的语句序列 
*属性             - (C++11 起) 任意数量的属性,应用于形参 
*类型说明符序列    - 形参声明的一部分,与在函数形参列表中相同 
*声明符           - 形参声明的一部分,与在函数形参列表中相同 
*抽象声明符       - 无名形参声明的一部分,与在函数形参列表中相同 
*/

         A try block begins with the keyword try, followed by a block of statement sequences enclosed in curly braces. A try block is followed by one or more catch clauses. Each catch clause consists of three parts: the keyword catch, a declaration of a single type or a single object enclosed in parentheses --- known as an exception specifier, and a statement block, usually enclosed in curly braces. If a catch clause is selected to handle the exception, the associated block statement is executed. Once the execution of the catch clause ends, program flow continues immediately with
the statement immediately following the last catch clause.

        The formal parameters of a catch clause (type-specifier-sequence and declarator, or type-specifier-sequence and abstract-declarator) determine what type of exception causes entry into this catch clause. It cannot be an rvalue reference type, an abstract class, an incomplete type, or a pointer to an incomplete type (pointers to (possibly cv-qualified) void are permitted, however. If the type of the formal parameter is an array type or a function type, it is treated as the corresponding pointer type (similar to a function declaration).

        2.2 Dynamic exception description-throw

        The throw exception specifier lists the exceptions that the function may throw directly or indirectly.

/*显式动态异常说明
*类型标识列表 - 逗号分隔的类型标识列表,后附省略号(...)的类型标识表示包展开 (C++11 起) 
*/
throw(类型标识列表(可选))     //(C++11 中弃用)(C++17 中移除) 

        This declaration can only be used as a function type, function pointer type, function reference type, member function pointer type function, variable, non-static data member declarator, top-level function declarator and formal parameter declarator or Occurs on the return type declarator. 

void f() throw(int);            // OK:函数声明
void (*pf)() throw (int);       // OK:函数指针声明
void g(void pfa() throw(int));  // OK:函数指针形参声明
typedef int (*pf)() throw(int); // 错误:typedef 声明

        Usually sometimes we also directly call the throw exception specifier in the body of the try block function to throw an exception object.

void f() throw(int);            // 函数声明,定义异常抛出为int型
void func() {
    try {
        f();    //隐式,间接
    } catch (int& obj) {
        std::cout << " the exception info: " << obj << '\n';
    }
}

void func() {
    try {
        throw std::string("test");   //主动抛出异常对象,显式
    } catch (std::string& obj) {
        std::cout << " the exception info: " << obj << '\n';
    }
}

         For more details about the throw exception specifier, see the following chapters.

        2.3 Standard exception system

        C++ allows any type of exception to be thrown, and it is generally recommended to throw a type derived from std::exception. std::exception is the exception base class provided by the standard library. It provides a consistent interface for the exception class set. All exceptions generated by the standard library inherit from std::exception . The inheritance system of the entire standard exception class is shown in the figure below.

         Most of these exception classes are well-known by name, and the definition of the base class std::exception is also very simple. Just like the most basic class members we customize, it provides brief member functions (construction, virtual destruction, copy assignment and a function that returns exception description information):

//成员函数
(构造函数)   构造异常对象                (公开成员函数) 
(析构函数)   [虚] 析构该异常对象         (虚公开成员函数) 
operator=   复制异常对象                (公开成员函数) 
what        [虚] 返回解释性字符串        (虚公开成员函数) 

//类声明
namespace std {
  class exception {
  public:
    exception() noexcept;
    exception(const exception&) noexcept;
    exception& operator=(const exception&) noexcept;
    virtual ~exception();
    virtual const char* what() const noexcept;
  };
}

        2.4 Try ... catch throws exception object processing

        C++ exceptions can be caught by a catch expression of the same type as the thrown exception, or by a catch(...) expression that can catch any type of exception. If the type of the exception thrown is a class that also has a base class or multiple subclasses, then receiving a reference to this type or a base class of this type can catch the exception. Note that when an exception is caught by reference, it is bound to the actual exception object that was thrown.

#include <iostream>
#include <vector>
 
void g4() {
    try {
        std::cout << "Throwing an integer exception...\n";
        throw 42;   //主动抛出int型对象
    } catch (int i) {
        std::cout << " the integer exception was caught, with value: " << i << '\n';
    }
    //
    try {
        std::cout << "Creating a vector of size 5... \n";
        std::vector<int> v(5);
        std::cout << "Accessing the 11th element of the vector...\n";
        std::cout << v.at(10);          // vector::at() 抛出 std::out_of_range
    } catch (const std::exception& e) { // 按基类的引用捕获
        std::cout << " a standard exception was caught, with message '"
                  << e.what() << "'\n";
    }
}

        Exceptions are thrown by throwing objects. The type of this object determines which processing code should be activated. The selected handler is the one in the call chain that matches the object type and is closest to where the exception was thrown. When any statement in the try compound statement throws an exception of type E, it is matched against the formal parameter type T of each catch clause in the processing block sequence, in the order in which the catch clauses are listed. An exception is matched if any of the following is true:

  • E is the same type as T (top-level cv-qualifiers on T are ignored)
  • T is an lvalue reference to (cv-qualified) E
  • T is an unambiguous public base class of E
  • T is a reference to an unambiguous public base class of E
  • T is (cv-qualified) U or const U& (since C++14), and U is a pointer or pointer to member (since C++17) type, and E is also capable of passing one or more of the following conversions a pointer or pointer to member (since C++17) type that converts to U other than a pointer conversion, qualifying conversion, function pointer conversion (since C++17) to pointer to private, protected, or ambiguous base class
  • T is a pointer or pointer to member, or reference to a const pointer (since C++14), and E is std::nullptr_t.
try {
    throw E;
} catch (const std::overflow_error& e) {
    // 若 E 抛出 std::overflow_error 则执行之(“相同类型”规则)
} catch (const std::runtime_error& e) {
    // 若 E 抛出 std::underflow_error 则执行之(“基类”规则)
} catch (const std::exception& e) {
    // 若 E抛出 std::logic_error 则执行之(“基类”规则)
} catch (...) {
    // 若 E 抛出 std::string 或 int 或任何其他无关类型则执行之
}

        Note in particular that the catch-all clause catch (...) matches any type of exception. If present, it (catch-all) must be the last catch clause in the sequence of processing blocks . Catch-all blocks can be used to ensure that it is impossible for an uncaught exception to escape from a function that provides a non-throwing guarantee.

catch (...) {
    //our code
}

        A catch clause that catches all exceptions matches any type of exception. If catch(...) is used in conjunction with other catch clauses, it must be the last, otherwise, any catch clauses following it will not be matched.

        When catch only needs to know the type of exception in order to handle the exception, the exception specifier can omit the formal parameter name; if the processing code needs information other than the type of exception that has occurred, the exception specifier includes the formal parameter name, and catch uses this Access the exception object by name.

void g1()
{
    try {
        //...code
    } catch (const std::exception& ) {
        // 仅需要匹配发生了那种类型异常,异常处理与对象信息无关要紧
    } catch (...) {
        // 强制处理异常,与对象类型及信息无关要紧!
    }
}

        2.5 Exception capture processing level

        If no match is found after testing all catch clauses, propagation of the exception continues to the surrounding try block, as described in the throw expression. If there are no enclosing try blocks left, std::terminate is executed (in this case, it is implementation-defined whether stack unwinding is done at all: throwing an uncaught exception is allowed to cause the program to terminate without calling any destructors).

void f()
{
    try{
        std::invalid_argument e("test");
        throw e;    //主动抛出一个异常
    }catch (const std::exception& e){
        //类型不匹配被跳过
    }
}

void g()
{
    try {
        f();
    } catch (const std::overflow_error& e) {
        // ...
    } catch (const std::runtime_error& e) {
        // ...
    } catch (const std::exception& e) {
        // ...
    } catch (...) {
        // ...,这里捕获
    }
}

        An exception specifier of a base class can be used to catch an exception object of a derived type, and the static type of the exception specifier determines the actions that the catch clause can perform. If the thrown exception object is of a derived class type, but is handled by a catch that accepts a base class type, then the catch must not use any members specific to the derived class.

        If a derived class catch clause is placed after a base class catch clause, the derived class catch clause is never executed. When organizing exception types into class hierarchies, the designer should carefully choose the level of granularity at which exceptions are handled by the application.

void g1()
{
    try {
        //...code
    } catch (const std::exception& e) {
        // 若 f() 抛 std::runtime_error 则执行
    } catch (const std::runtime_error& e) {//warning: exception of type 'std::runtime_error' will be caught by earlier handler [-Wexceptions]
        // 死代码!
    }
}

        2.6 Throwing Object Method

        If the catch clause handles exceptions of types that are related by inheritance, it should define its formal parameters as references.

        If the catch parameter is a reference type, the catch object directly accesses the exception object, and the static type of the catch object can be different from the dynamic type of the exception object referenced by the catch object.

        If the exception specifier is not a reference, the catch object is a copy of the exception object, and if the catch object is an object of the base class type and the exception object is of a derived type, the exception object is split into its base class subobjects. Objects (as opposed to references) are not polymorphic at that point. When using a virtual function through an object rather than a reference, the static type of the object is the same as the dynamic type, and the same is true for the function being virtual. Dynamic binding occurs only when called by reference or pointer, and no dynamic binding occurs when called by an object.

        When entering a catch clause, if its formal parameter is the base class of the exception type, it is copy-initialized from the base class subobject of the exception object. Otherwise, it is copy-initialized from the exception object (this copy is subject to the copy-elimination rules). If the formal parameter of the catch clause is a reference type, any changes made to it will be reflected in the exception object, and if the exception is rethrown with throw;, it can be observed by another processing block. If the parameter is not a reference, any changes to it are local and its lifetime ends when the processing block exits.

#include <string>
void g2()
{
    try {
        std::string("abc").substr(10); // 抛出 std::length_error
    // } catch (std::exception e) { // 从 std::exception 基类复制初始化
    //     std::cout << e.what(); // 丢失来自 length_error 的信息
    // }
    } catch (const std::exception& e) { // 多态对象基类的引用
        std::cout << e.what(); // 打印来自 length_error 的信息
    }
}

        Starting from C++11, in the catch clause, std::current_exception can be used to capture exceptions into a std::exception_ptr, and std::throw_with_nested can be used to build nested exceptions.

        2.7 The use of jump syntax is strictly prohibited in try blocks and processing blocks

        The goto and switch statements cannot be used to transfer control into try blocks and processing blocks. If a goto is used to exit a try block, and if the destructor of any block-scope automatic variable executed by this goto exits by throwing an exception, those exceptions are caught by the try block in which the variable is defined:

class A1{};
class A2{};

void g3()
{
    int i=0;
    label:
    try {
        A1 a1;
        try {
            A2 a2;
            if(i<2)
                goto label; // 销毁 a2,然后销毁 a1,再跳到 label
        } catch (...) {  } // 捕捉来自 a2 析构函数的异常
    } catch (...) {  } // 捕捉来自 a1 析构函数的异常
}

        In addition to throwing or rethrowing an exception, a catch clause following an ordinary try block (not a function try block) can exit via return, continue, break, goto, or by reaching the end of its compound statement. In any of these cases, the exception object is destroyed (unless there is an instance of std::exception_ptr referring to it).

        2.8 Exception capture and exception handling separation

         In C++'s try ... catch exception handling, it is necessary for the problem detection part to throw an object to the processing code. Through the type and content of the object, the exception information is determined and processed. Through exceptions, we can separate problem detection and problem solving. A part of the program can detect problems that cannot be solved by this part. This problem detection part can pass the problem to other parts that are ready to deal with the problem, so that the problem detection part of the program You don't have to know how to deal with problems.

class exception_def: public std::exception{};    //自定义异常类

void f(const exception_def& e)
{
    //code,专门处理异常的函数
}

void g5() {
    try {
        exception_def e;
        throw e;    //传递一个自定义对象
    } catch (const exception_def& e) { // 多态对象派生类的引用
        f(e);
    } catch (const std::exception& e) { // 多态对象基类的引用
        std::cout << e.what();
    }
}

        In the try block, when the throw is executed, the statement following the thrown object will not be executed, but the control will be transferred from the thrown object execution statement to the matching catch, which can be a local catch in the same function , or in another function that directly or indirectly calls the function in which the exception occurred. 

        2.9 Array and function type transfer

        In c/c++ compilation, when an array or function type argument is passed, the argument is automatically converted to a pointer. The same automatic conversion will take place for thrown objects, so there are no exceptions for array or function types. Conversely, if an array is thrown, the thrown object is converted to a pointer to the first element of the array.

void g6() {
    try {
        int excep[2]={1,2};
        throw excep;    //转换为指针
    } catch (int e[]) { // 转换为指针
        //
    } catch (const std::exception& e) { // 多态对象基类的引用
        std::cout << e.what(); 
    }
}

       Similarly, if a function is thrown, the function is converted to a pointer to the function, rather than creating a function object directly to pass.

class OperObj
{
public:
    void operator()(){};
};

void g6() {
    try {
        OperObj e;
        throw e;    //转换为指针
    } catch (OperObj& e) { // 转换为指针
        e();    //
    } catch (const std::exception& e) { // 多态对象基类的引用
        std::cout << e.what(); 
    }
}

        2.10 Exceptions and pointers

        Throwing a pointer with a throw expression always has trouble with dereferencing the pointer in the throw. The result of dereferencing a pointer is an object whose type matches that of the pointer. If the pointer points to a type in the inheritance hierarchy, the type of the object pointed to by the pointer may be different from the type of the pointer. Regardless of the actual type of the object, the type of the exception object matches the static type of the pointer. If the pointer is a pointer to a base class type pointer to a derived class object, then that object will be split and only the base class part will be thrown.

class exception_PTR: public std::exception{};

void g8() {
    std::exception *eptr= new exception_PTR();
    try {
        //do something
        throw eptr;    //抛出局部指针
    } catch (const std::exception* e) { // 多态对象基类的引用
        //只能使用基类的成员函数,派生类内重载的不可用
        std::cout << e->what(); 
    }
    if(nullptr!=eptr){
        delete eptr; 
        eptr = nullptr;
    }
}

It is always wrong to throw a pointer to a local object if the pointer         itself is thrown, for the same reason that it is wrong to return a pointer to a local object from a function. exist.

void g8() {
    try {
        char* e = new char[10];
        memcpy(e,"test",5);
        throw e;    //抛出局部指针
        delete[] e; //被跳过
        e = nullptr;
    } catch (char* e) { // 指针异常对象处理
        std::cout << std::string(e); 
        //再处理e指针释放已经晚了
    } catch (const std::exception& e) { // 多态对象基类的引用
        std::cout << e.what(); 
    }
}

        If a pointer to a local object is thrown, and the handling code is in another function, the object pointed to by the pointer will no longer exist when the handling code is executed. Even if the processing code is in the same function, you must be sure that the object pointed to by the pointer exists at the catch. If the pointer points to an object in a block exited before the catch, then the local object will be destroyed before the catch.
        Throwing pointers is generally a bad idea: throwing pointers requires that the object pointed to by the pointer exists anywhere that the corresponding processing code exists.

        2.11 Custom exceptions are best inherited from standard exception classes        

        In practice, many applications throw expressions whose base type comes from some inheritance hierarchy. Standard exceptions are defined in an inheritance hierarchy, and the standard exception classes can be used in many applications. In addition, the exception hierarchy is augmented by deriving additional types from the exception class or intermediate base classes. These newly derived classes can represent exception types specific to the application domain. If we customize the exception type, it is best to create an exception class based on the standard exception.

class myExcep : public exception {};
class myExcep : public runtime_error{};
.....

class myExcep : public std::runtime_error {
public:
    explicit myExcep (const std::string &s) : std::runtime_error(s) { };
};

         Application-specific exception types are defined by deriving from the standard exception classes. Like any hierarchy, exception classes can be thought of as organized in layers. As the layers deepen, each layer becomes a more specific anomaly. For example, the first and most general level in the hierarchy is represented by the exception class, and when objects of this type are caught, all we know is that something went wrong. The second layer specializes exceptions into two broad categories: runtime errors and logic errors. It is better not to directly inherit exception for user-defined exception classes, but to subdivide them to represent events in a more specialized layer. For example the myExcep class represents application-specific things that might go wrong at runtime.

class myExcep : public std::runtime_error {
public:
    explicit myExcep (const std::string &s) : std::runtime_error(s) { };
};

void myExcepf()
{
    try{
        //our code
    }catch (const myExcep& e){
    }catch (const std::runtime_error& e){
    }catch (const std::exception& e){
    }
};

        Users use their own exception classes in the same way as standard library classes. One part of the program throws an object of one of these types, and another part of the program catches and handles the indicated problem.

        Also throwing objects with static typing is not a problem. When an exception is thrown, the object that will be thrown is usually constructed at the point of the throw, which represents what went wrong, so we know the exact exception type.

void g4() {
    try {
        std::cout << "Throwing an integer exception...\n";
        throw 42;   //主动抛出int型对象
    } catch (int i) {
        std::cout << " the integer exception was caught, with value: " << i << '\n';
    }
}

        2.12 "Stack unwinding" of exception handling

        When an exception is thrown, the execution of the current function will be suspended and the matching catch clause will be searched. It first checks to see if the throw itself is inside the try block, and if so, checks the catch clauses associated with that catch to see if one of them matches the thrown object. If a matching catch is found, handle the exception; if not, exit the current function (release the internals of the current function and revoke the local object), and continue to search in the calling function.
        If the call to a function that throws an exception is in a try block, the catch clause associated with that try is checked. If a matching catch is found, the exception is handled; if no matching catch is found, the calling function also exits, and continues to search in the function that called this function.
        This process, called stack unwinding, continues up the chain of nested function calls until a catch clause is found for the exception. As long as it finds a catch clause that can handle the exception, it enters the catch clause and continues execution in the handling code. When the catch ends, execution continues at the point immediately after the last catch clause associated with the try block.

void f()
{
    try{
        std::invalid_argument e("test");
        throw e;    //主动抛出一个异常
    }catch (const std::exception& e){    //[1]
        //类型不匹配被跳过
    }
}

void g()
{
    try {
        f();
    } catch (const std::overflow_error& e) {//[2]
        // ...
    } catch (const std::runtime_error& e) {//[3]
        // ...
    } catch (const std::exception& e) {//[4]
        // ...
    } catch (...) {//[5]
        // ...,这里捕获
    }
    //terminate  //[6]
}

        Early exit from the function containing throw and possibly other functions in the call chain during stack unwinding. In general, these functions have created local objects that can be destroyed when exiting the function. When exiting a function due to an exception, the compiler ensures that local objects are deallocated appropriately. When each function exits, its local storage is freed, and before the memory is freed, all objects created before the exception occurred are deallocated. If the local object is of class type, the object's destructor is called automatically. Normally, the compiler does not destroy objects of built-in types.

        During stack unwinding, memory used by local objects is freed and destructors for local objects of the class type are run. If a block directly allocates a resource, and an exception occurs before the resource is freed, the resource will not be freed during stack unwinding. For example, a block can dynamically allocate memory by calling new, if the block exits due to an exception, the compiler will not delete the pointer, and the allocated memory will not be freed .
        Resources allocated by objects of class type are normally deallocated appropriately. Destructors for local objects are run. Resources allocated by objects of class type are usually freed by their destructors. It is recommended to use the programming technique of class-managed resource allocation in the face of exceptions.

        The program cannot fail to handle the exception, and the program terminates the program if there is an uncaught exception. An exception is an event of sufficient importance that the program cannot continue to execute normally. If no matching catch is found, the program calls the library function terminate.

        2.13 Class construction, destructor and exception handling

        Destructors should never throw exceptions, and destructors are often executed during stack unwinding. While executing the destructor, an exception has been thrown but not yet handled. If the destructor itself throws a new exception during this process, it will cause the standard library terminate function to be called. In general, the terminate function will call the abort function to force an abnormal exit from the entire program.
        Since the terminate function ends the program, it's generally a very bad idea for a destructor to do anything that might cause an exception. In practice, because the destructor frees the resource, it is less likely to throw an exception. Standard library types all guarantee that their destructors do not throw exceptions. When customizing a type, it is best not to throw an exception after the destructor, so as not to overwrite the old exception.

        Exceptions are the same as constructors, and unlike destructors, things that are done inside constructors often throw exceptions. If an exception occurs while constructing a function object, the object may be only partially constructed and some of its members may have been initialized while others had not been initialized before the exception occurred. Even if the object is only partially constructed, it is guaranteed that the constructed members will be destroyed appropriately.

class ExcepTest
{
public:
    ExcepTest();
    ~ExcepTest();
private:
    char* p1;
    char* p2;
    std::string *p3;
    std::string *p4;
};

ExcepTest::ExcepTest()
{
	try {//防止构造时异常出现内存泄漏
        //do something
	}
	catch (...) {
		delete p4; p4 = nullptr;//不良排版,为了节省行数显示
        delete p3; p3 = nullptr;
        delete p2; p2 = nullptr;
        delete p1; p1 = nullptr;
	}
}
ExcepTest::~ExcepTest()
{
    if (nullptr != p4) 	{ delete p4; p4 = nullptr;}//不良排版,为了节省行数显示
    if (nullptr != p3) 	{ delete p3; p3 = nullptr;}
    if (nullptr != p2) 	{ delete p3; p3 = nullptr;}
    if (nullptr != p1) 	{ delete p1; p3 = nullptr;}
}

        Exceptions can occur in constructors, or while processing constructor initializers. The constructor initializer is processed before entering the constructor function body, and the catch clause inside the constructor function body cannot handle exceptions that may occur while processing the constructor initialization. In order to handle exceptions from constructor initializers, the constructor must be written as a function try block. A set of catch clauses can be integrated with a function using a function test block.

class ExcepTest2
{
public:
    //构造函数要处理来自构造函数初始化式的异常,最好是将构造函数编写为函数测试块。
    ExcepTest2(char* pc_, int* pi_) 
    try : p1(pc_),p2(pi_){

    }
    catch(const std::exception& e)
    {
        destroy();
        std::cerr << e.what() << '\n';
    };
    ~ExcepTest2(){
        destroy();
    };
private:
    void destroy(){
        if(nullptr!=p2){ delete p2; p2 = nullptr;};
        if(nullptr!=p1){ delete p1; p1 = nullptr;};
    }
private:
    char* p1;
    int* p2;
};

        Similarly, exceptions may occur when initializing elements of arrays or other container types, and it is also guaranteed that constructed elements will be destroyed appropriately.

        2.14 Rethrowing exceptions

        It is possible that a single catch cannot fully handle an exception. After taking some corrective action, catch may determine that the exception must be handled by a function higher up in the function call chain, and catch may pass the exception up the function call chain by rethrowing. A rethrow is a throw not followed by a type or expression:

throw;

        An empty throw statement will rethrow the exception object, which can only appear in a catch or a function called from a catch. The terminate function is called if an empty throw is encountered while the processing code is inactive.

class my_error : public std::exception
{
    public:
    enum status{client_error,server_error,char_error,port_error};
    void reset_status(status st_){ st = st_;};
    private:
    status st;
};

void g10()
{
    try {
        throw my_error();// 抛出 my_error
    } catch (my_error &eObj) { // specifier is a reference type
        //异常判断,继续排除
        eObj.reset_status(my_error::server_error);// 修改异常对象
        std::cout <<"my_error reset\n";
        throw ; // 直接抛给上层调用
    } catch (const std::exception& e) { // 多态对象基类的引用
        std::cout << e.what(); 
    }
}

void g11()
{
    try {
        g10();
    }catch(...)
    {
        std::cout <<"error from g10\n";
    }
}
//
g11();
//out log
my_error reset
error from g10

        Although rethrowing does not specify its own exception, an exception object is still passed up the chain, and the exception thrown is the original exception object, not the catch parameter. When the catch parameter is of the base class type, we do not know the actual type thrown by the rethrowing expression, which depends on the dynamic type of the exception object, not the static type of the catch parameter. For example, a rethrow from a catch with a base type parameter may actually throw an object of a derived type. In general, catch can change its formal parameters. If catch rethrows an exception after changing its formal parameters, those changes will only be propagated if the exception specifier is a reference. 

        catch(...) is often used in conjunction with a rethrowing expression, catch does whatever local work it can, then rethrows the exception:

void g12() {
    try {
        // 异常捕获触发
    }
    catch (...) {
        // 不再本函数处理,抛给上层函数统一处理
        throw;
    }
}

        2.15 Managing Resource Allocation with Classes

        To ensure that the destructor runs correctly, making the program more exception-safe (exception-safe means that the program operates correctly even if an exception occurs). In this case, the "safety" of the destructor comes from the guarantee that "if an exception occurs, any resources that were allocated are properly deallocated". By defining a class to close the allocation and release of resources, you can ensure that resources are released correctly. This technique is often referred to as "Resource Allocation Is Initialization," or RAII for short.

unique_ptr   (C++11) 拥有独有对象所有权语义的智能指针 (类模板) 
shared_ptr   (C++11) 拥有共享对象所有权语义的智能指针 (类模板) 
weak_ptr     (C++11) 到 std::shared_ptr 所管理对象的弱引用 (类模板) 
auto_ptr     (C++17中移除) 拥有严格对象所有权语义的智能指针 (类模板) 

        Resource management classes should be designed so that constructors allocate resources and destructors release them. When you want to allocate resources, you define an object of this type. If no exception occurs, the resource is released when the object that acquired the resource goes out of scope. More importantly, if an exception occurs after an object is created but before it goes out of scope, the compiler guarantees that the object is destroyed as part of unwinding the scope in which the object was defined. 

3. Exception handling auxiliary keywords

        3.1 noexcept specifier

        See " c/c++ development, unavoidable custom class types (Part 7). A few ounces of silver_py_free-IOT Intelligent Blog-CSDN Blog"Chapter "1. noexcept specifier and class member function".

        3.2 Dynamic exception description-throw

        Dynamic exception description throw (before c++17), lists the exceptions that may be thrown directly or indirectly by the function.

  • If a function's declaration lists the type T in its dynamic exception specification, then the function may throw an exception of that type or a type derived from that type.
  • A dynamic exception declaration (i.e. throw()) without a list of type identifiers is not thrown.
  • A function with a no-throws dynamic exception specification does not allow any exceptions.
  • Incomplete types, pointers or references to incomplete types other than cv void*, and rvalue reference types cannot appear in the exception specification.
  • If array and function types are used, they are adjusted to the corresponding pointer types.
  • Parameter packs are allowed. (since C++11)
  • Dynamic exception declarations are not considered part of the function type.
  • If the function throws an exception of a type not listed in its exception specification, the function std::unexpected is called.
  • By default this function calls std::terminate, but it can be replaced (via std::set_unexpected) with a user-supplied function which may call std::terminate or throw an exception.
  • If the exception specification accepts an exception thrown from std::unexpected, then stack unwinding continues as usual. If it is not accepted, but the exception specification allows std::bad_exception, then std::bad_exception is thrown. Otherwise, std::terminate is called.
#include <exception>
#include <assert.h>
static_assert(__cplusplus < 201703,"ISO C++17");
 
class X {};
class Y {};
class Z : public X {};
class W {};
 
void func1() throw(X, Y) 
{
    int n = 0;
    if (n) throw X(); // OK
    if (n) throw Z(); // OK
    throw W(); // 将调用 std::unexpected()
}
 
void throw_test()
{
    std::set_unexpected([]
    {
        std::cout << "unexpected!" << std::endl; // 需要清除缓冲区
        std::abort();
    });
    func1();
}

        It is generally not recommended to actively and explicitly call throw in the function try block to throw an exception object, but to use an indirect and implicit way to throw an exception.

        3.3 throw and potential exceptions

        Every function f, function pointer pf, and member function pmfpointer has a "potential exception set", which consists of the types that may be thrown. A collection of all types indicates that any exception may be thrown. For implicitly declared special member functions (constructors, assignment operators, and destructors), and for inherited constructors (since C++11), the set of potential exceptions is the set of potential exceptions of all functions they would call The union of: non-variant non-static data members, constructors/assignment operators/destructors (also default argument expressions) of direct base classes and, where appropriate, virtual base classes.

        Each expression e holds a set of potential exceptions. If e is a core constant expression, the set is empty, otherwise the set is the union of the potentially exceptional sets of all direct subexpressions of e (including default argument expressions), plus another of the following depending on the form of e Collection merged.

1) 如果 e 是一个函数调用表达式,令 g 代表被调用的函数、函数指针或成员函数指针,那么: 
◦如果 g 的声明使用了动态异常说明,那么添加 g 的潜在异常集合到集合;
◦如果 g 的声明使用了 noexcept(true),那么集合为空;(C++11 起) 
◦否则,该集合是所有类型的集合。

2) 如果 e 隐式调用了某个函数(它是运算符表达式且该运算符被重载,是 new 表达式且其分配函数被重载,或是完整表达式且调用了临时量的析构函数),那么该集合是这个函数的集合。

3) 如果 e 是一个 throw 表达式,那么该集合是以其操作数所初始化的异常,或对于重抛出表达式(无操作数者)是所有异常的集合。

4) 如果 e 是对多态类型引用的 dynamic_cast,那么该集合由 std::bad_cast 组成。

5) 如果 e 是应用到多解引用指向多态类型指针的 typeid,那么该集合由 std::bad_typeid 组成。

6) 如果 e 是一个拥有非常量数组大小的 new 表达式,且选择的分配函数拥有非空的潜在异常集合,那么该集合由 std::bad_array_new_length 组成。 (C++11 起) 

void f() throw(int);                // f() 的集合是“int”
void g();                           // g() 的集合是所有类型的集合
struct A { A(); };                  // “new A”的集合是所有类型的集合
struct B { B() noexcept; };         // “B()”的集合为空
struct D() { D() throw (double); }; // “new D”的集合是所有类型的集合

         3.4 throw and member functions

        All implicitly declared member functions and inherited constructors (since C++11) have exception descriptions, and the exception descriptions of member function declarations follow the function parameter list, and the options are as follows:

◦如果潜在异常的集合是类型全集,那么隐式异常说明允许所有异常(该异常说明被认为存在,即使它不可用代码表达,且表现如同无异常说明) (C++11 前)是 noexcept(false) (C++11 起)。
◦否则,如果潜在异常的集合非空,那么隐式异常说明列出每个来自该集合的类型
◦否则,隐式异常说明是 throw() (C++11 前)noexcept(true) (C++11 起)。
//
struct A
{
    A(int = (A(5), 0)) noexcept;
    A(const A&) throw();
    A(A&&) throw();
    ~A() throw(X);
};
 
struct B
{
    B() throw();
    B(const B&) = default; // 异常说明是“noexcept(true)”
    B(B&&, int = (throw Y(), 0)) noexcept;
    ~B() throw(Y);
};
 
int n = 7;
struct D : public A, public B
{
    int * p = new (std::nothrow) int[n];
    // D 拥有下列隐式声明的成员:
    // D::D() throw(X, std::bad_array_new_length);
    // D::D(const D&) noexcept(true);
    // D::D(D&&) throw(Y);
    // D::~D() throw(X, Y);
};

         Note that in a const member function declaration, the exception specification follows the const qualifier.

        3.5 Exception description throw and virtual function

        The exception description of the virtual function in the base class can be different from the exception description of the corresponding virtual function in the derived class. However, the exception specification for a derived class virtual function must be as strict as, or more restrictive than, the exception specification for the corresponding base class virtual function. This restriction ensures that when a derived class virtual function is called with a pointer to a base class type, the derived class exception specification does not add a new throwable exception. For example:

class Base {
public:
    virtual double f1(double) throw (){};
    virtual int f2(int) throw (std::logic_error){};
    virtual std::string f3() throw(std::logic_error, std::runtime_error){};
};
class Derived : public Base {
public:
    // error: 异常列表范围收窄
    // double f1(double) throw (std::underflow_error){};
    // ok: 相同异常列表
    int f2(int) throw (std::logic_error){};
    // ok: 异常列表范围变大
    std::string f3() throw (){};
};

        The declaration of f1 in the derived class is wrong because its exception specification adds an exception to those listed for the base class version of f1. Derived classes cannot add exceptions to the exception specification list because users of the inheritance hierarchy should be able to write code that depends on the specification list. If function calls are made through base class pointers or references, users of these classes should only be concerned with exceptions specified in the base class. PS: The keyword noexcept is also similar.
        The exceptions thrown by the derived class are limited to those listed by the base class, so you know which exceptions you must handle when you write your code. Code can rely on the fact that the list of exceptions in the base class is a superset of the list of exceptions that the derived class version of the virtual function can throw. 

        The exception specification is part of the function type. In this way, the exception specification can also be provided in the definition of the function pointer:

void (*pf)(int) throw(runtime_error);

        This statement means that pf points to a function that accepts an int value, that function returns a void object, and that this function can only throw exceptions of type runtime_error. If no exception specification is provided, the pointer can point to a function of a matching type capable of throwing an exception of any type. 

        3.6 std::terminate

//定义于头文件 <exception>
void terminate();   //(C++11 前) 
[[noreturn]] void terminate() noexcept; //(C++11 起) 

         The C++ runtime calls std::terminate() when the program cannot continue for any of the following reasons:

1) 未捕捉抛出的异常(此情况下是否进行任何栈回溯是实现定义的)

2) 在处理仍未经由异常捕捉的异常时(例如由某局部对象的析构函数,或构造 catch 子句参数的复制构造函数抛出),由异常处理机制所直接调用

3) 静态或线程局域 (C++11 起)对象的构造函数或析构函数抛出异常

4) 以 std::atexit 或 std::at_quick_exit (C++11 起)注册的函数抛出异常

5) 违反动态异常说明,并执行了 std::unexpected 的默认处理函数 (C++17 前)

6) std::unexpected 的非默认处理函数抛出了违背先前所违背动态异常规定的异常,若这种规定不包含 std::bad_exception (C++17 前)

7) 违反 noexcept 说明(此情况下是否进行任何栈回溯是实现定义的)(C++11 起)

8) 为一个不保有被捕获异常的对象调用 std::nested_exception::rethrow_nested(C++11 起)

9) 从 std::thread 的起始函数抛出异常(C++11 起)

10) 可结合的 std::thread 被析构或赋值(C++11 起)

11) std::condition_variable::wait、 std::condition_variable::wait_until 或 std::condition_variable::wait_for 无法达成其前条件(例如若重锁定互斥抛出)(C++11 起)

12) 并行算法所调用的函数经由未捕捉异常退出,且该执行策略指定了终止。(C++17 起)

        std::terminate() can also be called directly from a program.

        In any case, std::terminate calls the currently installed std::terminate_handler. The default std::terminate_handler calls std::abort.

*若析构函数在栈回溯时重设 terminate_handler ,且后面的回溯导致调用 terminate ,则在 throw 表达式的结尾安装的处理函数会得到调用。(注意:重抛出是否应用新处理函数是有歧义的) (C++11 前)

*若析构函数在栈回溯时重设 terminate_handler ,则若后面的栈回溯导致调用 terminate ,调用哪个处理函数是未指定的。 (C++11 起)

        3.7 std::unexpected

        std::unexpected() is called by the C++ runtime when a dynamic exception specification is violated: an exception of this type is thrown by a function whose exception specification prohibits this type of exception.

//定义于头文件 <exception> 
void unexpected();               //(C++11 前) 
[[noreturn]] void unexpected();  //(C++11 起)(弃用)(C++17 中移除) 

         std::unexpected() can be called directly from a program. In either case, std::unexpected calls the currently installed std::unexpected_handler. The default std::unexpected_handler calls std::terminate.

*若析构函数在栈回溯期间重置 unexpected_handler 且之后的回溯导致调用 unexpected ,则将调用于 throw 表达式结尾安装的处理函数。(注意:重抛出是否应用新的处理函数是有歧义的) (C++11 前)

*若析构函数在栈回溯期间重置 unexpected_handler ,则若之后的回溯导致调用 unexpected ,则调用哪个处理函数是未指定的。

Fourth, about the assertion - macro assert

        4.1 Macro assert definition

        C/C++ defines a macro assert that is often used when debugging programs:

//定义于头文件 <cassert>或 <assert.h> 
#ifdef NDEBUG
#define assert(condition) ((void)0)
#else
#define assert(condition) /*implementation defined*/
#endif 

        As the program runs it evaluates the expression inside the parentheses, and if the expression is FALSE (0), the program reports an error and terminates execution. If the expression is not 0, execution continues with the following statement. As shown above, the definition of the macro assert depends on another macro called NDEBUG that is not defined in the standard library.

        4.2 The macro assert is used in conjunction with the NDEBUG macro

        If no macro named NDEBUG is defined, assert compares its argument (which must have a scalar type) to zero for equality. If equal, assert prints an implementation-specified diagnostic on standard error and calls std::abort. The diagnostic information is required to contain the text of expression, as well as the values ​​of the predefined variables __func__ and (since C++11) the predefined macros __FILE__, __LINE__.

        NDEBUG is defined at the assert position of the last definition or redefinition (that is, the header file <cassert> or <assert.h> is included), and the expression assert(E) is guaranteed to be a constant subexpression, that is, after E is converted to bool according to the context , is a constant subexpression that evaluates to true.

void assertion_test(void)
{
    assert(1+1==2);
    std::cout << "Execution continues past the first assert\n";
    assert(1+1==3);                 //断言捕获,直接调用abort告警及退出
    std::cout << "Execution continues past the second assert\n";
    //do other 
    std::cout << "dosomething!\n";  //程序退出,还得到执行  
}

int main(int argc, char* argv[])
{
    assertion_test();
    std::cout <<"main finish!\n";
    return 0;
}

        Compile and test g++ main.cpp test*.cpp -o test.exe -std=c++11, output

 Compile and test g++ main.cpp test*.cpp -o test.exe -std=c++11 -DNDEBUG, output

         Therefore, the assertion macro assert is used in conjunction with the macro DNDEBUG, and is mainly used for exception capture in the debugger. Most of the time it is used for function entry formal parameter monitoring, such as for pointer verification.

struct TestA
{
    /* data */
    char* pc;
};

void test_assert(TestA* ptr)
{
    if(nullptr==ptr) return;    //可预见的直接函数自行判定
    assert(nullptr!=ptr->pc);   //形参内部的采用断言检测
    //do other 
}

        4.3 Pay attention to the exception of the macro assert replacement expression

        Because assert(E) is a function-like macro, when using it, you need to pay attention to the fact that when the constant expression E is replaced by precompilation, it will not be protected by parentheses and cannot correctly implement our code wishes:

#include <complex>
void test_assert_def(void)
{
    std::complex<double> c;
    // assert(c == std::complex<double>{0, 0}); // 错误,未被括号保护的逗号都被转译成宏参数的分隔符
    assert((c == std::complex<double>{0, 0})); // OK
};

        The disadvantage of using assert is that frequent calls will greatly affect the performance of the program and increase additional overhead. After debugging, you can disable the assert call by inserting #define NDEBUG before the statement containing #include <assert.h>, the sample code is as follows, or add -DNDEBUG to the compilation command, or add NDEBUG to the compilation project:

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

        4.4 Compile assertion - static_assert

        The macro assert is mainly used to make assertions at runtime. In the C++11 standard, the static_assert macro was introduced to implement compile-time assertions. static_assert is very simple to use. It receives two parameters, one is an assertion expression, which usually needs to return a bool value; the other is a warning message, which is usually a string. The usage of this keyword is similar to the built-in BOOST_STATIC_ASSERT assertion mechanism of the open source library Boost before the C++11 standard, using the expression 1/(e) to determine:

#define assert_static(e) \
	do{ \
		enum { assert_static = 1/(e) }; \ 
	)while(0)

        static_assert can be used in any namespace. During the preprocessing stage, the static_assert macro will be expanded into a C keyword named _Static_assert. This keyword is used in C code in a "function call"-like form that accepts a constant expression as its first argument. When the program is compiled, the compiler evaluates this expression and compares the result with the number zero. If the two are equal, the program terminates compilation, and the error information specified by the second parameter is combined with the assertion failure information for output. If the two are not equal, the program will be compiled normally, and the C code corresponding to the keyword will not generate any corresponding machine instructions.

    int bit_deal (int& a) ( 
    	static_assert(sizeof(int) >=4,"the parameters of bit should have at least 4 width.");
    );

        The program restricts that when the program is compiled, the width of the int type on its platform needs to be greater than or equal to 4 bytes, the compilation will be terminated, and the corresponding error message will be printed out:

error: static assertion failed: "the parameters of bit should have at least 4 width."

         Such an error message is very clear, and it is also very helpful for programmers to troubleshoot. Generally speaking, we use static assertions to check a series of requirements that a program needs to meet before it runs. As in the above example, through static assertion, developers can know in advance whether the program can work normally if it runs on the current platform.

        There are many similar use cases, such as judging the default symbolicity of the char type, or judging whether the width of the pointer type is equal to that of the int type, or judging whether the size of a certain structure meets the expected requirements, and so on. These are factors that may affect the correctness of the c/c++ program, and through static assertions, they can be detected in advance at compile time.

        It is generally suggested that it is usually a better choice to write static_assert outside the function body, which makes it easier for code readers to find that static_assert is an assertion rather than a user-defined function.

        In addition, it must be noted that the result of the assertion expression of static_assert must be an expression that can be calculated at compile time, that is, it must be a constant expression. If the reader uses a variable, it will cause an error like:

int bit_deal (int a) ( 
	static_assert( a>=1,"the parameters of a should >=1.");
);

        The parameter variable a is used above, so static_assert cannot be compiled. If you need to check variables, you need to implement runtime checks and use the assert macro. 

5. Exception handling suggestions

        [1] Distinguish between abnormal and non-abnormal

        Do not attribute business logic implementation errors to program errors. For example, if a certain data reaches the threshold, conditional judgment should be used to realize its business logic processing, and an exception should not be thrown to force the program to run. A proper distinction needs to be made between error handling (eg exceptions and error codes) and techniques (eg security guarantees, keywords, preconditions , postconditions) . .

        [2] Ensure that exception handling is exactly needed and not lazy

        Exception handling introduces efficiency costs. Effective use of exception handling requires an understanding of business logic and code logic: what happens when an exception is thrown, what happens when an exception is caught, and the meaning of the object used to pass the error. The type of this object determines which processing code should be activated. The selected handler is the one in the call chain that matches the object type and is closest to where the exception was thrown. Exceptions are thrown and caught in a manner similar to passing arguments to functions. An exception can be an object of any type that can be passed to a non-reference parameter, which means it must be possible to copy an object of that type.

        [3] Use logical conditional processing instead of exception processing for exhaustive exceptions

        When we can predict the error situation that will occur when the function is executed, and can handle it through the conditional branch, it is unnecessary to use exception handling.

        [4] Exception handling eliminates some exception levels after repeated debugging

        In project practice, it is usually not possible to introduce many exception handling levels at the beginning because of business scenarios and business logic understanding problems. With business digestion and version iteration, many exceptions are suppressed in advance, so the exception handling levels should also be removed simultaneously. .

6. Demo source code

        Compilation instructions: g++ main.cpp test*.cpp -o test.exe -std=c++11 (optional: -DNDEBUG)

        main.cpp

#include "test1.h"
#include <iostream>

int main(int argc, char* argv[])
{
    assertion_test();
    exception_test();
    std::cout <<"main finish!\n";
    return 0;
}

        test1.h

#ifndef _TEST_1_H_
#define _TEST_1_H_

void assertion_test(void);
void exception_test(void);
#endif //_TEST_1_H_

        test1.cpp

#include "test1.h"
#include <cassert>
#include <iostream>

void assertion_test(void)
{
    assert(1+1==2);
    std::cout << "Execution continues past the first assert\n";
    assert(1+1==3);                 //断言捕获,直接返回main退出
    std::cout << "Execution continues past the second assert\n";
    //do other 
    std::cout << "dosomething!\n";  //程序退出,还得到执行  
}

struct TestA
{
    /* data */
    char* pc;
};

void test_assert(TestA* ptr)
{
    if(nullptr==ptr) return;    //可预见的直接函数自行判定
    assert(nullptr!=ptr->pc);   //形参内部的采用断言检测
    //do other 
}

#include <complex>
void test_assert_def(void)
{
    std::complex<double> c;
    // assert(c == std::complex<double>{0, 0}); // 错误,未被括号保护的逗号都被转译成宏参数的分隔符
    assert((c == std::complex<double>{0, 0})); // OK
};

class myExcep : public std::runtime_error {
public:
    explicit myExcep (const std::string &s) : std::runtime_error(s) { };
};

void myExcepf()
{
    try{
        //our code
    }catch (const myExcep& e){
    }catch (const std::runtime_error& e){
    }catch (const std::exception& e){
    }
};

void f()
{
    try{
        std::invalid_argument e("test");
        throw e;
    }catch (const std::exception& e){
    }
}

void g()
{
    try {
        f();
    } catch (const std::overflow_error& e) {
        // ...
    } catch (const std::runtime_error& e) {
        // ...
    } catch (const std::exception& e) {
        // ...
    } catch (...) {
        // ...
    }
}

void g1()
{
    try {
        //...code
    } catch (const std::exception& e) {
        // 若 f() 抛 std::runtime_error 则执行
    } catch (const std::runtime_error& e) {
        // 死代码!
    }
}

#include <string>
void g2()
{
    try {
        std::string("abc").substr(10); // 抛出 std::length_error
    // } catch (std::exception e) { // 从 std::exception 基类复制初始化
    //     std::cout << e.what(); // 丢失来自 length_error 的信息
    // }
    } catch (const std::exception& e) { // 多态对象基类的引用
        std::cout << e.what(); // 打印来自 length_error 的信息
    }
}

class A1{};
class A2{};

void g3()
{
    int i=0;
    label:
    try {
        A1 a1;
        try {
            A2 a2;
            if(i<2)
                goto label; // 销毁 a2,然后销毁 a1,再跳到 label
        } catch (...) {  } // 捕捉来自 a2 析构函数的异常
    } catch (...) {  } // 捕捉来自 a1 析构函数的异常
}

#include <iostream>
#include <vector>
 
void g4() {
    try {
        std::cout << "Throwing an integer exception...\n";
        throw 42;   //主动抛出int型对象
    } catch (int i) {
        std::cout << " the integer exception was caught, with value: " << i << '\n';
    }
    //
    try {
        std::cout << "Creating a vector of size 5... \n";
        std::vector<int> v(5);
        std::cout << "Accessing the 11th element of the vector...\n";
        std::cout << v.at(10);          // vector::at() 抛出 std::out_of_range
    } catch (const std::exception& e) { // 按基类的引用捕获
        std::cout << " a standard exception was caught, with message '"
                  << e.what() << "'\n";
    }
}

class exception_def: public std::exception{};

void f(const exception_def& e)
{
    //code
}

void g5() {
    try {
        exception_def e;
        throw e;
    } catch (const exception_def& e) { // 多态对象派生类的引用
        f(e);
    } catch (const std::exception& e) { // 多态对象基类的引用
        std::cout << e.what(); 
    }
}

void g6() {
    try {
        int excep[2]={1,2};
        throw excep;    //转换为指针
    } catch (int e[]) { // 转换为指针
        //
    } catch (const std::exception& e) { // 多态对象基类的引用
        std::cout << e.what(); 
    }
}

class OperObj
{
public:
    void operator()(){};
};

void g7() {
    try {
        OperObj e;
        throw e;    //转换为指针
    } catch (OperObj& e) { // 转换为指针
        e();    //
    } catch (const std::exception& e) { // 多态对象基类的引用
        std::cout << e.what(); 
    }
}

class exception_PTR: public std::exception{};

void g8() {
    std::exception *eptr= new exception_PTR();
    try {
        //do something
        throw eptr;    //抛出局部指针
    } catch (const std::exception* e) { // 多态对象基类的引用
        //只能使用基类的成员函数,派生类内重载的不可用
        std::cout << e->what(); 
    }
    if(nullptr!=eptr){
        delete eptr; 
        eptr = nullptr;
    }
}

void g9() {
    try {
        char* e = new char[10];
        memcpy(e,"test",5);
        throw e;    //抛出局部指针
        delete[] e; //被跳过
        e = nullptr;
    } catch (char* e) { // 指针异常对象处理
        std::cout << std::string(e); 
        //再处理e指针释放已经晚了
    } catch (const std::exception& e) { // 多态对象基类的引用
        std::cout << e.what(); 
    }
}

class ExcepTest
{
public:
    ExcepTest();
    ~ExcepTest();
private:
    char* p1;
    char* p2;
    std::string *p3;
    std::string *p4;
};

ExcepTest::ExcepTest()
{
	try {//防止构造时异常出现内存泄漏
        //do something
	}
	catch (...) {
		delete p4; p4 = nullptr;//不良排版,为了节省行数显示
        delete p3; p3 = nullptr;
        delete p2; p2 = nullptr;
        delete p1; p1 = nullptr;
	}
}
ExcepTest::~ExcepTest()
{
    if (nullptr != p4) 	{ delete p4; p4 = nullptr;}//不良排版,为了节省行数显示
    if (nullptr != p3) 	{ delete p3; p3 = nullptr;}
    if (nullptr != p2) 	{ delete p3; p3 = nullptr;}
    if (nullptr != p1) 	{ delete p1; p3 = nullptr;}
}

class ExcepTest2
{
public:
    ExcepTest2(char* pc_, int* pi_) 
    try : p1(pc_),p2(pi_){

    }
    catch(const std::exception& e)
    {
        destroy();
        std::cerr << e.what() << '\n';
    };
    ~ExcepTest2(){
        destroy();
    };
private:
    void destroy(){
        if(nullptr!=p2){ delete p2; p2 = nullptr;};
        if(nullptr!=p1){ delete p1; p1 = nullptr;};
    }
private:
    char* p1;
    int* p2;
};

class my_error : public std::exception
{
    public:
    enum status{client_error,server_error,char_error,port_error};
    void reset_status(status st_){ st = st_;};
    private:
    status st;
};

void g10()
{
    try {
        throw my_error();// 抛出 my_error
    } catch (my_error &eObj) { // specifier is a reference type
        //异常判断,继续排除
        eObj.reset_status(my_error::server_error);// 修改异常对象
        std::cout <<"my_error reset\n";
        throw ; // 直接抛给上层调用
    } catch (const std::exception& e) { // 多态对象基类的引用
        std::cout << e.what(); 
    }
}
void g11()
{
    try {
        g10();
    }catch(...)
    {
        std::cout <<"error from g10\n";
    }
}

void g12() {
    try {
        // 异常捕获触发
    }
    catch (...) {
        // 不再本函数处理,抛给上层函数统一处理
        throw;
    }
}

#include <exception>
#include <assert.h>
static_assert(__cplusplus < 201703,"ISO C++17");
 
class X {};
class Y {};
class Z : public X {};
class W {};
 
void func1() throw(X, Y) 
{
    int n = 0;
    if (n) throw X(); // OK
    if (n) throw Z(); // OK
    throw W(); // 将调用 std::unexpected()
}
 
void throw_test()
{
    std::set_unexpected([]
    {
        std::cout << "unexpected!" << std::endl; // 需要清除缓冲区
        std::abort();
    });
    func1();
}

class Base {
public:
    virtual double f1(double) throw (){};
    virtual int f2(int) throw (std::logic_error){};
    virtual std::string f3() throw(std::logic_error, std::runtime_error){};
};
class Derived : public Base {
public:
    // error: 异常列表范围收窄
    // double f1(double) throw (std::underflow_error){};
    // ok: 相同异常列表
    int f2(int) throw (std::logic_error){};
    // ok: 异常列表范围变大
    std::string f3() throw (){};
};

void exception_test(void)
{
    g7();
    g11();
    throw_test();
}

Guess you like

Origin blog.csdn.net/py8105/article/details/129738592