7

My understanding about exception handling is very limited. While I find it is easy to throw an exception (or I can pack it using expected<T> for later consumption), I have very little idea about what to do with an exception.

Presently my knowledge is limited to

  • clean my own resources and rethrow the exception to be handled at appropriate location. e.g.

    ptr p = alloc.allocate(n);
    try
    {
       uninitialized_copy(first,last,p);//atomic granularity, all or none
    }
    catch(...)
    {
        alloc.deallocate(p,n);
        throw;
    }
    

But I guess, this can be equivalently transformed in a RAII pattern as

alloc_guard<ptr> p{alloc.allocate(n)};
uninitialized_copy(first,last,p.get());
p.commit();
  • catch the exception at a top level, compose & print a nice message and exit.e.g.

    int main(int argc,char** argv)
    {
       try
       {
           app_t the_app(argc,argv);
           the_app.run();
       }
       catch(std::runtime_error& e)
       {
          //instead of what, I can also compose the mesage here based on locale.
          std::cout<<e.what()<<std::endl;
       }
    }
    

So, all I do is in a top level function such as main catch the exception and print an appropriate message and close.

While implementing a library with a nice set of API using various external libraries as back end for implementation, I realized that third party library exceptions are part of my API specification, as they cross my library boundary and land in user code!

So, my library API leaked all exceptions from external libraries (and each one having their own exception hierarchy) that I was using to the user code.

This leads to my question, what all can be done when I catch any exception ?

More specifically,

  • Can I translate the caught exception from external library to my own exception and throw that in a generic way (say the mapping between third party library exception hierarchy and my exception API is provided as a mpl::map) ?
  • Can I do something more useful than printing a message/call stack, say resume the function at the throw site with different input parameter (say when I get that a file_not_found or disk_error, re-run the function with a different file)?
  • Any other pattern which is valuable to know?

Thanks

abir
  • 1,797
  • 14
  • 26

3 Answers3

4

Additional to what nogard said I want to add the following:

  1. Exceptions should be used for exceptional things. Things that should not happen.
  2. If you encounter an exception, log it at least somewhere. This might help you finding a bug.
  3. Try to resolve the error at the point where you caught the exception.
  4. If this is not possible, try to stay in a consistent state where the application can continue.
  5. If this is not possible - think about gracefully terminating.
  6. Notify the user that something out of the ordinary happened.

Final advice - keep your error handling consistent. This includes translating exceptions from third party libraries to your exception hierarchy.


Answers to comments:

2) The exception should contain information about what went wrong. This could be just the type or some additional information. Logging these at the customer allows you to get more information on what actually went wrong beyond to what the customer tells you. Perhaps he misused your application, found another use case or you simply have a bug (e.g. a not initialized variable). But with this extra information you have a location where something went wrong and some information on what went wrong. This helps you to deduce the source of the error and thus find the bug.

3) this actually depends on the error that is occurring. E.g. you try to access a configuration file that is not there --> you create a new one with default values. The client tries to open a database for write access, but it is write protected. You decline opening, return to a valid state and tell the client that the db is write protected. You run out of memory and can't continue? Log this (beware - you have no spare memory so your logging should already have reserved some memory upfront for this use case) and gracefully shut down the application. Perhaps, if possible, notify the client.

Regarding code from other libraries: there's no other way then checking each function call to another library for the exceptions it might return and catching them. Once caught, you can transfer the information from that exception into one of yours and throw that exception (or resolve it somehow else)

Tobias Langner
  • 10,634
  • 6
  • 46
  • 76
  • 1. I understand it. 2. I can log at destructor easily, but how that helps me to find *bug*? 3. How to do this exactly? I am looking for some code. Where I `caught` an exception is certainly not same as where it was thrown. 4,5,6 my example shows the usage, and only one which I can implement. Finally for translating exceptions, how exactly will I code it? Looking for little more concrete example! – abir Aug 13 '13 at 06:13
  • Your points 1-6 are the probably best and most concise exception-howto that I've ever read. – Damon Aug 13 '13 at 12:10
2

This is very big subject.

  1. I doubt you can easily translate third party exceptions into your own exceptions, and moreover personally I see no need to implement this behavior. Since 3rd party library is a part of your implementation, which is not exposed to the public API, why would you expose all of its exceptions (even through some mapping)? If one day you stick to another 3rd party library implementing the same stuff - would you like to redesign whole exception hierarchy? I think not. API of your library must not be fragile, so I would propose not to map external exceptions to your own ones.

  2. You can map 3rd party exceptions to your hierarchy following ways:

    • Don't wrap anything at all. I mean you don't have to throw anything just because 3rd library does so. You can catch that exception and handle it, or return error code, or change the state appropriately. There are many other possibilities rather than rethrowing always.

    • You don't have to make one-to-one translations for all 3rd party exceptions. If you use library AAA internally, then you can have single AAAException representing many exceptions coming from that library.

  3. Valuable to know: always catch exceptions by const reference:

    catch (const exception & ex)

This subject is very big, I hope my answer helps to understand it.

Answers to the comments:

  1. If I do not map third party exceptions to my own API (need not to be one to one), they leak to client code - No, they don't, that's the whole point! You must catch them inside your library and then decide what to do with catched exceptions: throw your own exception, return error code, notify client listener, log error etc...

    try {
        3rdpatry.call();
    } catch (const 3rdpartyException & ex) {
        // throw YourException(ex.what());
        // listener.notify(some_error)
        // return some_code
    }
    
  2. Catching by const reference is not to prevent slicing at all. There is nice discussion here that explains that.

Community
  • 1
  • 1
nogard
  • 9,432
  • 6
  • 33
  • 53
  • If I do not map third party exceptions to my own API (need not to be one to one), they leak to client code. Why client should learn how to get relevant information from a third party library, as e.source_type() to get source information, if the exception is `boost::lexical_cast` that I used internally (and may not use in future) for a user provided data? Also, why should I catch an exception by constant reference? I catch an exception to add additional context information, or compose a message out of stored information & print. e.g. in `boost::exception` `e << file_name("foo.txt");` – abir Aug 13 '13 at 05:54
  • If you catch by constant reference, you prevent slicing. See http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/ (Guru-Question) or http://stackoverflow.com/questions/18143604/why-use-a-const-non-reference-when-const-reference-lifetime-is-the-length-of-the for some implications on const references. Additionally, there's a chapter on that in Item 13 of More Effective C++ on that matter. – Tobias Langner Aug 13 '13 at 06:07
  • Why slicing is going to happen when I catch exception by reference (as my example shows) rather than constant reference. None of the links gives any clue. The book also talks about catching exception by reference (and not constant reference). boost::exception also suggests same, and it specifically advises to put additional information when available, and compose the message of what at the call site to prevent any exception at the constructor of exception as well as to allow locale specific message. Can you elaborate little about constant reference and slicing? – abir Aug 13 '13 at 06:24
  • What's the advantage of constant reference over non-constant reference? It's quite fair to give ownership of the exception object to the catch block. – Potatoswatter Aug 13 '13 at 06:29
  • @TobiasLangner `const` never prevents slicing. The examples there do not apply here because the exception object lives outside any local scope and is passed as an lvalue to each `catch` block that receives it. – Potatoswatter Aug 13 '13 at 06:33
  • @nogard Are you sure that a const exception will not get caught by a nonconst reference? My understanding was that exceptions are copied to some location (outside stack, probably somewhere in free memory) when you `throw` it. So cv qualifiers are irrelevant, and you can freely take reference to the exception object even outside the function(i.e. in catch block of another call stack). The example you provided, the catch statement executes perfectly with my gcc 4.7.3 compiler. – abir Aug 13 '13 at 09:10
  • @abir: Yes, you are right, my mistake. I updated the answer to refer the SO discussion about catching exceptions by const reference. – nogard Aug 13 '13 at 09:24
  • 1
    I think the only reason for having that `const` is the ability to bind to a rvalue. Slicing doesn't happen or isn't relevant. If you catch a `base` reference for a `derived`, you aren't prepared to handle the lost information anyway, so it doesn't matter if it's there or not. As far as destructors go, with the `const`, the original destructor is called. Without it, the compiler will bail out on "illegal initialization of non-const reference" if a temporary is thrown and use virtual dispatch otherwise. So unless the library is "broken" (no virtual destructor base class), there's no real issue. – Damon Aug 13 '13 at 12:08
2

Top-level catch is usually not enough for larger applications. You need to catch exception when you can do something about it, but there are principally only few ways in what to do with an exception:

  1. Recover if you can. - (e.g.: Check for update -> Network connection could not be opened exception -> ignore and don't download the update now.)
  2. Tell the user to choose how to recover. (e.g.: Save file -> File cannot be created exception -> tell user to choose different filename or cancel)
  3. Log and exit. This is the top-level catch-all scenario.
Juraj Blaho
  • 13,301
  • 7
  • 50
  • 96