开发者

Is my use of C++ catch clause, families of exception classes, and destruction sane?

开发者 https://www.devze.com 2023-02-01 07:53 出处:网络
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, an

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 mor开发者_高级运维e 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.


This pattern is definitely safe.

There are special rules that extend the lifetime of a thrown object. Effectively, it exists as long as it is being handled and it is guaranteed to exist until the end of the last catch block that handles it.

One very common idiom, for example, to derive custom exceptions from std::exception, override its what() member function, and catch it by reference so that you can print error messages from a wide variety of exceptions with one catch clause.


No, you're definitely doing it right. See http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.13 , and the rest of the FAQ chapter for that matter.


Yes. So far so good.

Personally I use std::runtime_error as the base of all exception calsses. It handles error messages etc.

Also don't declare more exceptions that you need to. Define an exception only for things that can actually be caught and fixed. Use a more generic exception for things that can not be caught or fixed.

For example: If I develop a library A. Then I will have an AException derived from std::runtime_error. This exception will be used for all generic exceptions from the library. For any specific exceptions where the user of the library can actually catch and do something (fix or mitigate) with the exception then I will create a specific exception derived from AException (but only if there is something that can be done with the exception).


Indeed, Sutter and Alexandrescu recomend this pattern in their 'C++ Coding Standards'.

0

精彩评论

暂无评论...
验证码 换一张
取 消