Does the current standard guarantee to invoke the destructor for subobject when the initialization of the complete object throws an exception

Viewed 104

In section except.ctor, the rule states:

  1. As control passes from the point where an exception is thrown to a handler, objects with automatic storage duration are destroyed by a process, specified in this subclause, called stack unwinding.
  2. [...]
  3. If the initialization or destruction of an object other than by delegating constructor is terminated by an exception, the destructor is invoked for each of the object's direct subobjects and, for a complete object, virtual base class subobjects, whose initialization has completed ([dcl.init]) and whose destructor has not yet begun execution, except that in the case of destruction, the variant members of a union-like class are not destroyed. [ Note: If such an object has a reference member that extends the lifetime of a temporary object, this ends the lifetime of the reference member, so the lifetime of the temporary object is effectively not extended. — end note ] The subobjects are destroyed in the reverse order of the completion of their construction. Such destruction is sequenced before entering a handler of the function-try-block of the constructor or destructor, if any.

According to bullet 3, which states can be also considered as a part of the process of stack unwinding. Since the first rule sounds like that the invocation of stack unwinding is only for the object with automatic storage duration. Doesn't that stack unwinding invoke for the object with dynamic storage duration or others duration?

#include <iostream>
struct A{
  ~A(){
    std::cout<<"invoke\n";
  }
};
struct B{
 B(){
   throw 0;
 }
  A a;
};
int main(){
   try{  
      auto ptr = new B{};
    }catch(int){
   }
}

Obviously, the subobject a occupies the storage within the complete object of the type B which has the dynamic storage duration. In my mind, A's destructor should be invoked since it has been completely constructed in the initialization of B. Both GCC and Clang agree that. Is it an extension of these compilers? How to interpret it?

1 Answers

By the plain wording of [except.ctor]/3, it applies to objects with any storage duration, as long as they are subobjects of an object whose construction was terminated by an exception. This should not be controversial.

However, the wording of [except.ctor] has changed over time, and this seems to have created some issues which the OP has noticed. The background is that the wording used to be simliar to the current wording, with "stack unwinding" only referring to the destruction of automatic objects, and someone noticed that this created an inconsistency, where, when no matching handler is found, it is not guaranteed whether stack unwinding occurs, but it is guaranteed that subobject destruction occurs (since there is nothing in the text to say that it may not happen if a handler is not found). This was CWG 1774. CWG agreed that this inconsistency was undesirable (and perhaps it was unintentional, though that page doesn't say). So the wording was changed so that subobject destruction would be an aspect of "stack unwinding", and thus covered under [except.handle]/9, instead of being a separate process. But then later, to resolve a different DR that has nothing to do with exceptions, the old wording was added back, and I'm 90% sure that it was unintentional. The intent of the resolution to CWG2256 was just to avoid discrimination against trivially destructible objects, and not to actually change anything about exception handling.

Therefore, the current wording is defective, and the standard should still be read as if "stack unwinding" includes subobject destruction (i.e., the resolution to CWG 1774 stands). You might even be able to fix this editorially (i.e., submit a pull request against the standard source).

Related