Is catching an exception by reference dangerous?

Solution 1:

It is indeed safe - and recommended - to catch by const reference.

"e is actually placed on the stack of some_function()"

No it's not... the object actually thrown is created in an unspecified area of memory reserved for use by the exception handling mechanism:

[except.throw] 15.1/4: The memory for the exception object is allocated in an unspecified way, except as noted in 3.7.4.1. The exception object is destroyed after either the last remaining active handler for the exception exits by any means other than rethrowing, or the last object of type std::exception_ptr (18.8.5) that refers to the exception object is destroyed, whichever is later.

If a local variable is specified to throw, it's copied there-to if necessary (the optimiser may be able to directly create it in this other memory). That's why...

15.1/5 When the thrown object is a class object, the constructor selected for the copy-initialization and the destructor shall be accessible, even if the copy/move operation is elided (12.8).


If that's not clicked, it might help to imagine implementation vaguely like this:

// implementation support variable...
thread__local alignas(alignof(std::max_align_t))
    char __exception_object[EXCEPTION_OBJECT_BUFFER_SIZE];

void some_function() {
    // throw std::exception("some error message");

    // IMPLEMENTATION PSEUDO-CODE:
    auto&& thrown = std::exception("some error message");
    // copy-initialise __exception_object...
    new (&__exception_object) decltype(thrown){ thrown };
    throw __type_of(thrown);
    // as stack unwinds, _type_of value in register or another
    // thread_local var...
}

int main(int argc, char **argv)
{
    try {
        some_function();
    } // IMPLEMENTATION:
      // if thrown __type_of for std::exception or derived...
      catch (const std::exception& e) {
        // IMPLEMENTATION:
        // e references *(std::exception*)(&__exception_object[0]);
        ...
    }
}

Solution 2:

You have to catch by reference, otherwise you couldn't possibly get the correct dynamic type of the object. As for its lifetime, the standard guarantees, in [except.throw],

The exception object is destroyed after either the last remaining active handler for the exception exits by any means other than rethrowing, or the last object of type std::exception_ptr (18.8.5) that refers to the exception object is destroyed, whichever is later