Search code examples
c++exceptionlanguage-lawyerstandards

Function style exception rethrow. Bug in standard/compilers?


Consider this artificial example.
Two identical destructors, one catches the exception inside function style try/catch, one scope based try/catch.

#include <iostream>

struct A {
    ~A() noexcept try {
        throw std::runtime_error("~A");
    } catch (std::exception const &e) {
        std::cout<<__LINE__<<" "<<e.what()<<std::endl;
    }
};

struct B {
    ~B() noexcept {
        try {
            throw std::runtime_error("~B");
        } catch (std::exception const &e) {
            std::cout<<__LINE__<<" "<<e.what()<<std::endl;
        }
    }
};

int main() try {
    // A a; // std::terminate is called, exception not caught at line 26. The try-catch block in main is not relevant.
    B b;
    return 0;
} catch (std::exception const &e) {
    std::cout<<__LINE__<<" "<<e.what()<<std::endl;
}

Godbolt link: example

So class B's destructor catches the exception and prints: 16 ~B Were in case of class A it calls terminates and prints:

terminate called after throwing an instance of 'std::runtime_error'
  what():  ~A
7 ~A

May someone hint thy is this happening? (seems implicit rethrow at the end of function style try/catch, both on clang and GCC) Does standard somehow specifying this behaviour? Any quote/link may be very helpfull. Thanks in advace.


Solution

  • A function-level catch on a constructor or destructor automatically rethrows the current exception upon exit:

    https://en.cppreference.com/w/cpp/language/function-try-block

    Every catch-clause in the function-try-block for a constructor must terminate by throwing an exception. If the control reaches the end of such handler, the current exception is automatically rethrown as if by throw;. The return statement is not allowed in any catch clause of a constructor's function-try-block.

    Reaching the end of a catch clause for a function-try-block on a destructor also automatically rethrows the current exception as if by throw;, but a return statement is allowed.

    So, your noexcept on ~A() is lying to the compiler. If a noexcept function exits because of an uncaught exception, terminate() is called:

    https://en.cppreference.com/w/cpp/language/noexcept_spec

    Non-throwing functions are permitted to call potentially-throwing functions. Whenever an exception is thrown and the search for a handler encounters the outermost block of a non-throwing function, the function std::terminate is called