Search code examples
c++exceptionlanguage-lawyerdestructor

Compiler differences when throwing exception from a destructor


I have found following C++ question (cppQuiz #323):

According to the C++17 standard, what is the output of this program?

#include <iostream>
#include <stdexcept>

struct A {
    A(char c) : c_(c) {}
    ~A() { std::cout << c_; }
    char c_;
};

struct Y { ~Y() noexcept(false) { throw std::runtime_error(""); } };

A f() {
    try {
        A a('a');
        Y y;
        A b('b');
        return {'c'};
    } catch (...) {
    }
    return {'d'};
}

int main()
{
    f();
}

The expected answer for the question is "bcad" with a reference to [except.ctor] in the recent standard. However, according to godbolt, clang 16.0 returns "bad" and gcc 13.1 returns "bacd".

Do anyone know what happens?


Solution

  • The output ought to be bcad.

    When the first return statement is reached, the result object of the call to f, which is the temporary object materialized in main from the discarded-value expression f(), will be initialized before any local variables or temporaries are destroyed. See [stmt.return]/3.

    There are no temporary objects in the function itself that would be destroyed.

    So then local variables are destroyed in reverse order of initialization.

    First b is destroyed, outputting b.

    Then y is destroyed and throws the exception. The exception is caught by the catch handler and therefore stack unwinding will happen.

    Stack unwinding will destroy objects of automatic storage duration in the scope, as well as the already constructed result object, in reverse order of their construction, as long as they haven't been destroyed yet. See [except.ctor]/2 which actually has a very similar example and was clarified by CWG 2176.

    b and y have already been destroyed. So only the result object and a remain. The result object was constructed after a.

    Stack unwinding will therefore start by destroying the already constructed result object, outputting c.

    Then a is destroyed, outputting a, and stack unwinding concludes.

    After executing the catch handler, the final return statement is reached, which once again constructs the result object, which in turn is destroyed at the end of the full-expression f() in main to output d.


    The compiler seem to not have implemented the defect report for CWG 2176 yet and before that it wasn't clear what the behavior should be.

    As for why they haven't implemented it yet, I would guess that this is such an unusual scenario, that they would consider it low priority. Having a throwing destructor is unusual and as the question shows, it is unlikely that the user would have the same expectation on the order of destruction as the standard does. However, I think that Clang's behavior of completely skipping the destruction of the already constructed result object is bit problematic.