为什么c++抛出异常后还能对函数内的局部对象进行析构?
天蓬老师
天蓬老师 2017-04-17 11:43:14
0
3
643

C++是如何确保出了异常还能调用析构函数的

天蓬老师
天蓬老师

欢迎选择我的课程,让我们一起见证您的进步~~

reply all(3)
迷茫

How to ensure? StandardEnsure. Because this is stipulated by the standard.

The following is excerpted from C++ 11 Standard (draft N3690)

15.2 Constructors and destructors [except.ctor]

Not only ensures that it will be destroyed, but also stipulates the order of destruction.

1) As control passes from the point where an exception is thrown to a handler, destructors are invoked for all automatic objects constructed since the try block was entered. The automatic objects are destroyed in the reverse order of the completion of their construction.

What’s special is that if an exception occurs during construction or destruction, its sub-object can also ensure that it is destroyed correctly. And what about the object itself? It doesn't exist when you construct it, so there's nothing to worry about. The destruction situation will be explained later.

2) An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects (excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor (12.6.2) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object's destructor will be invoked. If the object was allocated in a new-expression, the matching deallocation function (3.7.4.2, 5.3.4, 12.5), if any, is called to free the storage occupied by the object.

This whole process is called "stack unwinding", which is translated as: stack unwinding.

3) The process of calling destructors for automatic objects constructed on the path from a try block to the point where an exception is thrown is called “stack unwinding.” If a destructor called during stack unwinding exits with an exception, std::terminate is called (15.5.1). [Note: So destructors should generally catch exceptions and not let them propagate out of the destructor. —end note]

Pay attention to the "Note". The standard imposes requirements on the compiler. The destructor must be responsible for itself.

It is precisely because of the guarantee of stack unwinding as the basis that we have the well-known RAII technology.

Also note that what if an exception is thrown during stack unwinding? You can only call std::terminate:

15.5.1 The std::terminate() function [except.terminate]

2) In such cases, std::terminate() is called (18.8.3). In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before std::terminate() is called. In the situation where the search for a handler (15.3) encounters the outermost block of a function with a noexcept-specification that does not allow the exception (15.4), it is implementation-defined whether the stack is unwound, unwound partially, or not unwound at all before std::terminate() is called. In all other situations, the stack shall not be unwound before std::terminate() is called. An implementation is not permitted to finish stack unwinding prematurely based on a determination that the unwind process will eventually cause a call to std::terminate()

stack unwinding 的过程并不保证做完,但最终肯定是要调用 std::terminate 来终止。

左手右手慢动作

Calling the destructor of the stack memory object after an exception is thrown is stipulated in the C++ standard. The destructor is not explicitly called. The compiler and runtime environment naturally know when the destructor should be called, as long as they are implemented in accordance with the C++ standard. As for how to implement it, I think it's not difficult. You just need to mark the initialized objects and call their destructors one by one when handling exceptions.

Peter_Zhu

Quoting the description from Wikipedia, it is clearer than our explanation. The boldface is added by me:

throw

throw is a C++ keyword, and the following operands form a throw statement, which is syntactically similar to a return statement. The throw statement must be contained in a try block; it can be contained in a try in an outer function on the call stack.

When a throw statement is executed, the result of its operand is copied as an object and constructed into a new object, which is placed in a special location in the memory (neither the heap nor the stack. On Windows, it is placed in the "Thread Information Block TIB" ). This new object is type matched one by one by the catch statements corresponding to the try at this level; if the match is unsuccessful, it is type matched with the outer catch statement of this function in turn; if it cannot be successfully matched with the catch statement within this function, Then the recursion returns to the function at the upper level of the call stack and continues to match the catch statement starting from the function call point. This process is repeated until a catch statement is successfully matched or until the main function main() cannot handle the exception.

Therefore, the exception object thrown by the throw statement is different from the general local object. Normal local objects will be destroyed when their scope ends. The exception object thrown by the throw statement resides in the memory space that can be accessed by all catch statements that may be activated.

The exception object thrown by the throw statement is destructed at the end of the successfully matched catch statement (even if the catch statement uses a non-reference value parameter type).

Since the throw statement makes a copy, the exception object should be copy-constructable. But for the Microsoft Visual C++ compiler, even if the copy constructor of the exception object is private, the exception object can still be thrown normally by the throw statement; but when the parameter of the catch statement is passed by value, the compiler will report an error at the catch statement :

cannot be caught as the destructor and/or copy constructor are inaccessible”。

When an expression is thrown, the static compile-time type of the thrown object will determine the type of the exception object.

Stack expansion

Stack unwinding (unwinding) means that after the current try...catch... block successfully matches or fails to match the exception object, from the throwing position of the exception object in the try block, to the beginning of the try block All local variables at which their respective constructors have been executed will be destructed in sequence in the reverse order of construction and generation. If the matching of the exception object thrown in the current function is unsuccessful, the local variables from the outermost try statement to the starting position of the current function body will also be destructed in reverse order to implement stack expansion, and then continue Return to the function at the upper level of the call stack and continue processing the exception starting from the function call point.

If the catch statement successfully matches the exception object, after completing the initialization of the parameters of the catch statement (completes the copy construction of the parameter object for the value-passed parameters), the try block at the same level will be executed to expand the stack.

When a thread is executed, the parameters, return address, local variables, etc. of the called function are saved on the function call stack (i.e., the thread runtime stack) in the order of function calls. The parameters and local variable names of the currently called function can overwrite the variables with the same name of the previously called function. It seems that only the names in the current function can be accessed, and the names inside the previously called function are not accessible, just like the tape is " Roll up”. During exception handling, the local variables of each called function are destructed in reverse order according to the order of function calls. It is like unrolling the "tape" that has been rolled up and erasing the data recorded on it. Therefore, "stack expansion" "Get the name.

Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template