Reputation: 12178
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?
Upvotes: 64
Views: 6472
Reputation: 106096
It is indeed safe - and recommended - to catch by const
reference.
"
e
is actually placed on the stack ofsome_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]);
...
}
}
Upvotes: 97
Reputation: 69882
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
Upvotes: 18
Reputation: 4926
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()
.
Upvotes: 8
Reputation:
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
Upvotes: 22