Is catching an exception by reference dangerous?

C++Exception

C++ Problem Overview


Please take a look at the following exception throwing and catching:

void some_function() {
    // Was std::exception("message") in original post, which won't compile
    throw std::runtime_error("some error message"); 
}

int main(int argc, char **argv) {
    try {
        some_function();
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        exit(1);
    }
    return 0;
}

Is it safe to catch the thrown exception by reference?

My concern is because the exception e is actually placed on the stack of some_function(). But some_function() has just returned, causing e to be destructed. So actually now e points to a destructed object.

Is my concern correct?

What is the correct way to pass the exception without copying it by value? Should I throw new std::exception() so it is placed in the dynamic memory?

C++ Solutions


Solution 1 - C++

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 - C++

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

Solution 3 - C++

Catching by const reference is exactly how exceptions should be caught. The exception object does not necessarily live 'on the stack'. The compiler is responsible for the appropriate magic to make this work.

On the other hand, your example cannot compile since std::exception may only be default-constructed or copy-constructed. In this case the what() method would return a pointer to an empty (c-style) string, which is not particularly useful.

Suggest you throw a std::runtime_error or std::logic_error as appropriate, or a class derived therefrom:

  • logic_error when the caller has requested something outside the design parameters of your service.
  • runtime_error when the caller has requested something reasonable but external factors prevent you from honouring the request.

http://en.cppreference.com/w/cpp/error/exception

Solution 4 - C++

From except.throw:

> Throwing an exception copy-initializes (8.5, 12.8) a temporary object, > called the exception object. The temporary is an lvalue and is used to > initialize the variable declared in the matching handler (15.3). If > the type of the exception object would be an incomplete type or a > pointer to an incomplete type other than (possibly cv-qualified) void > the program is ill-formed.

It's the act of throwing the exception that copies the exception object in the exceptions-area, outside of any stack. So it's perfectly legit, and advisable, to catch exception by reference, since the exception object lifetime will extend until the last possible catch().

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSomethingSomethingView Question on Stackoverflow
Solution 1 - C++Tony DelroyView Answer on Stackoverflow
Solution 2 - C++user1084944View Answer on Stackoverflow
Solution 3 - C++Richard HodgesView Answer on Stackoverflow
Solution 4 - C++SigiView Answer on Stackoverflow