当程序抛出一个异常时,程序暂停当前函数的执行过程并立即开始查找(look up)最邻近的与异常匹配的 catch 子句。
Ctrl+F5
执行(不调试)”时不会调用析构函数,但在“F5
调试”时,当报错“未经处理的异常”时,选择“F5
继续”,结果会调用析构函数;另一方面,如果我们使用 std::set_terminate 为 std::terminate 安装新的 std::terminate_handler ,那么就有可能调用析构函数了,比如以 std::exit 替换默认的 std::abort 作为新的 std::terminate_handler ,同时如果异常发生前创建的局部变量是 static
的,那么程序因未捕获的异常而终止时就会调用局部 static
变量的析构函数了,示例程序见 Cpp-Primer/Ch18_01_set_terminate.cpp at main · ltimaginea/Cpp-Primer · GitHub )。为了能够快速处理异常,编译器应该会做一定的记录工作:在每一个 try 语句块的进入点记录对应的 catch 子句能够处理的异常类型。如果发生异常,程序在运行期便可以根据记录的数据来快速查找(look up)是否存在与异常匹配的 catch 子句,从而快速处理异常。不同的编译器的具体策略会有所不同。
std::terminate : 终止当前的程序。默认情况下, std::terminate 会调用 std::abort 。当我们使用 std::set_terminate 为 std::terminate 安装新的 std::terminate_handler 时,新安装的 std::terminate_handler 最终应该终止程序,如果没有, std::abort 将会被自动调用以终止程序(经过使用 MSVC 和 g++ 测试,确实是这样。See: Unhandled C++ exceptions | Microsoft Docs )。
std::abort : 导致程序异常终止。它不进行清理工作:不会调用自动对象,静态对象和线程局部对象的析构函数。
std::exit : 导致程序正常终止。它会进行一些清理工作:会调用静态对象和线程局部对象的析构函数;但不进行栈展开(stack unwinding):不会调用自动对象的析构函数。
std::abort 和 std::exit 这两个函数都不会销毁自动对象,因为 stack unwinding 不会被执行起来。如果希望确保所有局部对象的析构函数被调用,应该运用异常机制(捕获异常)或正常返回,然后从 main() 退出程序。
Exceptions and stack unwinding in C++ | Microsoft Docs 的栈展开(stack unwinding)的描述如下:
In the C++ exception mechanism, control moves from the throw statement to the first catch statement that can handle the thrown type. When the catch statement is reached, all of the automatic variables that are in scope between the throw and catch statements are destroyed in a process that is known as stack unwinding. In stack unwinding, execution proceeds as follows:
try
statement by normal sequential execution. The guarded section in the try
block is executed.catch
clauses that follow the try
block are not executed. Execution continues at the statement after the last catch
clause that follows the associated try
block.throw
operand. (This implies that a copy constructor may be involved.) At this point, the compiler looks for a catch
clause in a higher execution context that can handle an exception of the type that is thrown, or for a catch
handler that can handle any type of exception. The catch
handlers are examined in order of their appearance after the try
block. If no appropriate handler is found, the next dynamically enclosing try
block is examined. This process continues until the outermost enclosing try
block is examined.terminate
is called. If an exception occurs after the exception is thrown but before the unwind begins, terminate
is called. In these cases, it is implementation-defined whether any stack unwinding occurs at all: throwing an uncaught exception is permitted to terminate the program without invoking any destructors.catch
handler is found, and it catches by value, its formal parameter is initialized by copying the exception object. If it catches by reference, the parameter is initialized to refer to the exception object. After the formal parameter is initialized, the process of unwinding the stack begins. This involves the destruction of all automatic objects that were fully constructed—but not yet destructed—between the beginning of the try
block that is associated with the catch
handler and the throw site of the exception. Destruction occurs in reverse order of construction. The catch
handler is executed and the program resumes execution after the last handler—that is, at the first statement or construct that is not a catch
handler. Control can only enter a catch
handler through a thrown exception, never through a goto
statement or a case
label in a switch
statement.