Search code examples
c++exceptionnoexcept

Should Lippincott functions be declared noexcept?


EDIT: Another way to ask this question, in perspective, is to ask: Should Lippincott functions "catch all"?

Should Lippincott functions be declared noexcept?, does it matter?

After all, a version of this function in which all exceptions are captured, by definition cannot produce an exception.

However, in all the examples I see online, the noexcept is never there and I was wondering if I was missing something.

Example case:

foo_Result lippincott() noexcept???
{
    try {
        throw;
    } catch (const MyException1&) {
        return FOO_ERROR1;
    } catch (const MyException2&) {
        return FOO_ERROR2;
    } catch (...) {
        return FOO_UNKNOWN;
    }
}

foo_Result foo_dothing() {
    try {
        foo::DoThing();
        return FOO_OK;
    }
    catch (...) {
        return lippincott();
    }
}

Solution

  • Not all catch (...) executions come from a C++ exception. It is typically advisable to rethrow the exception in any catch-all block. This would imply that lippincott should not be noexcept and also just not have the catch-all block.

    Specifically, in the ABI commonly used for C++ outside of Windows, forced unwinding may execute catch-all blocks:

    A catch-all block may be executed during forced unwinding. For instance, a longjmp may execute code in a catch(...) during stack unwinding. However, if this happens, unwinding will proceed at the end of the catch-all block, whether or not there is an explicit rethrow.

    In particular, with GCC on Linux, the blocks are executed, and if not rethrown, the application is terminated. Forced unwinding can happen even in code you completely control on POSIX if you ever call cancellation points (e.g. read).

    Aside from this, it's not uncommon for a library to execute user code (e.g. think qsort). You also usually don't want to suppress those exceptions.

    Therefore, the best generic option is to be transparent in catch-all blocks. Perform the cleanup you need. Then always rethrow.

    So your function would look something like:

    foo_Result lippincott()
    {
        try {
            throw;
        } catch (const MyException1&) {
            return FOO_ERROR1;
        } catch (const MyException2&) {
            return FOO_ERROR2;
        } catch (const FooBaseException&) {
            return FOO_UNKNOWN;
        }
    }
    

    GCC does allow catching forced unwinds, so if you really wanted a catch-all and the other consideration is discarded (e.g. no user callbacks), you can first catch abi::__forced_unwind and rethrow.