64

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?

SomethingSomething
  • 11,491
  • 17
  • 68
  • 126
  • 7
    note that the string constructor for std::exception is not standard, this is a MS (conforming?) extension. http://stackoverflow.com/questions/5157206/why-does-stdexception-have-extra-constructors-in-vc – Sigi Oct 28 '15 at 09:48
  • So how can you assign a message to the exception, so it is printed using `what()`? – SomethingSomething Oct 28 '15 at 09:56
  • 4
    @SomethingSomething you would use a derivative of `std::exception` that does allow passing the message to the constructor. `std::runtime_error` for example. And the convention of throwing subtypes is exactly the reason why you should catch by (const) reference. – eerorika Oct 28 '15 at 10:00
  • 1
    To write portable code you have to inherit from `std::exception` and write your own `what()` method, that is virtual in `std::exception` – Sigi Oct 28 '15 at 10:01
  • 3
    @SomethingSomething two ways: 1. use `std::runtime_error` or `std::logic_error` that provide that constructor and string message status or 2. do it yourself. See the answers to this question: http://stackoverflow.com/questions/8152720/correct-way-to-inherit-from-stdexception. – Sigi Oct 28 '15 at 10:10
  • Possible duplicate: http://stackoverflow.com/questions/33241984/c-throwing-class-members? – luk32 Oct 28 '15 at 15:02
  • @luk32 it is not a duplicate. This is about objects placed on stack and not about class members – SomethingSomething Oct 28 '15 at 19:17
  • A copy constructor could throw, which would call `std::terminate` I think. – spraff Oct 29 '15 at 12:46

4 Answers4

96

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]);
        ...
    }
}
Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • I think 15.1.3 is as much relevant, if not more. Especially "*Throwing an exception copy-initializes ( 8.5 , 12.8 ) a temporary object [...]*", so `e` never points to the object thrown, but to a copy - the exception object - which was used to initialize the argument of `catch`. – luk32 Oct 28 '15 at 15:04
  • 1
    @luk32: I'm not convinced: the important thing is that the exception object's lifetime outlasts the stack unwinding back to the relevant `catch` statement(s) - what bearing does 15.1.3 have on that? Still, feel free to upvote Sigismodo's answer, if you haven't already. Your own paraphrasing/conclusions of 15.1.3 is flawed IMHO - the exception object creation may be elided, so there's not necessary two distinct objects about which statements like "never points to the object thrown, but to a copy" can be accurately made. – Tony Delroy Oct 28 '15 at 16:58
  • 1
    Umm.. Ok, let me retract a bit, it's just as important. My point is that, whatever is thrown, might be dead within the catch scope. Semantically, the function ended. If compiler decides it wants to perform elision it's fine. The thing is, that *the exception object* is not what you pass to `throw` statement, but a *temporary* created from it. Their lifetime is not tied to each other. I updated both btw. – luk32 Oct 28 '15 at 17:35
  • @luk32: We're going round in circles, as *"the exception object is not what you pass to throw statement, but a temporary created from it. Their lifetime is not tied to each other."* makes the same "two distinct objects" presumption I've already commented on. *"might be dead within the catch scope"* repeats the concerns in the question; unnecessary ones given the exception object captures *by value*. Anyway, bottom line is I think adding 15.1.3 would dilute the focus of the answer, but with your comment we're covered either way. Re "I updated both btw" - I have no idea what you mean. Cheers – Tony Delroy Oct 29 '15 at 02:52
  • please someone tell me what is the reference link for this answer? – dev-masih Oct 29 '15 at 05:11
  • @TonyD no,no, :) I mean what is the reference for the quotes in this answer? – dev-masih Oct 29 '15 at 05:43
  • 1
    @MasihAkbari: links to draft C++ Standards are listed [here](http://stackoverflow.com/q/81656/410767). I believe I quoted from n3797. Formal, complete Standards documents are sold by the ISO. – Tony Delroy Oct 29 '15 at 06:10
  • "I updated both btw" -> upvoted. I was probably thinking about two things at once. To be clear I didn't touch a thing =), the comment sounded bad. Thanks for replies. Cheers. – luk32 Oct 30 '15 at 07:47
22

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

  • 4
    Well, you don't *have to*, it is legal not to do so. However, if you don't, you're already are aiming a gun at your own knee. You introduce a possible intermediate copy and more importantly object slicing if you catch by value. – luk32 Oct 28 '15 at 14:57
18

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

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
8

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().

Sigi
  • 4,826
  • 1
  • 19
  • 23