Once in a while, I notice some coding pattern that I’ve had for years and it makes me nervous. I don’t have a specific problem, but I also don’t remember enough about why I adopted that pattern, and some aspect of it seems to match some anti-pattern. This has recently happened to me WRT how some of my code uses exceptions.
The worrying thing involves cases where I catch an exception “by reference”, treating it in a similar way to how I’d treat a parameter to a function. One reason to do this is so I can have an inheritance hierarchy of exception classes, and specify a more general or more precise catch type depending on the application. For example, I might define…
class widget_error {};
class widget_error_all_wibbly : public widget_error {};
class widget_error_all_wobbly : public widget_error {};
void wibbly_widget ()
{
throw widget_error_all_wibbly ();
}
void wobbly_widget ()
{
throw widget_error_all_wobbly ();
}
void call_unknown_widget (void (*p_widget) ())
{
try
{
p_widget ();
}
catch (const widget_error &p_exception)
{
// Catches either widget_error_all_wibbly or
// widget_error_all_wobbly, or a plain widget_error if that
// is ever thrown by anything.
}
}
This is now worrying me because I’ve noticed that a class instance is constructed (as part of the throw) within a function, but is referenced (via the p_Exception catch-clause “parameter”) after that function has exited. This is normally an anti-pattern – a reference or pointer to a local variable or temporary created within a function, but passed out when the function exits, is normally a dangling reference/pointer since the local variable/temporary is destructed and the memory freed when the function exits.
Some quick tests suggest that the throw above is probably OK – the instance constructed in the throw clause isn’t destructed when the function exits, but is destructed when the catch-clause that handles it completes – unless the catch block rethrows the exception, in which case the next catch block does this job.
My remaining nervousness is because a test run in one or two compilers is no proof of what the standard says, and since my experience says that what I think is common sense is often different to what the language guarantees.
So – is this pattern of handling exceptions (catching them using a reference type) safe? Or should I be doing something else, such as…
- Catching (and explicitly deleting) pointers to heap-allocated instances instead of references to something that looks (when thrown) very like a temporary?
- Using a smart pointer class?
- Using “pass-by-value” catch clauses, and accepting that I cannot catch any exception class from a hierarchy with one catch clause?
- Something I haven’t thought of?
This is ok. It’s actually good to catch exceptions by constant reference (and bad to catch pointers). Catching by value creates an unnecessary copy. The compiler is smart enough to handle the exception (and its destruction) properly — just don’t try to use the exception reference outside of your catch block 😉
In fact, what I often do is to inherit my hierarchy from std::runtime_error (which inherits from std::exception). Then I can use
.what(), and use even fewer catch blocks while handling more exceptions.