views:

190

answers:

6

In C++, RAII is often advocated as a superior approach to exception handling: if an exception is thrown, the stack is unwound, all the destructors are called and resources are cleaned up.

However, this presents a problem with error reporting. Say a very generic function fails, the stack is unwound to the top level and all I see in the logs would be:

Couldn't read from socket: connection reset by peer.

...or any equally generic message. This doesn't say much about the context from which the exception is thrown. Especially if I'm running something like an event queue processing loop.

Of course I could wrap every call to socket reads with a try/catch block, catch the exception, construct a new one with more detailed context information and re-throw it, but it defeats the purpose of having RAII, and is slowly but surely becoming worse than handling return error codes.

What's a better way for detailed for error reporting in standard C++? I'm also open to suggestions involving Boost.

+3  A: 

I've never actually done this, but you could roll your own "stacktrace":

struct ErrorMessage {
    const char *s;
    ErrorMessage(const char *s) : msg(s) {}
    ~ErrorMessage() { if (s) std::cout << s << "\n"; }
    void done() { s = 0; }
};

void someOperation() {
    ErrorMessage msg("Doing the first bit");
    // do various stuff that could throw
    msg = "Doing the second bit";
    // do more stuff that could throw
    msg.done();
}

You can have several levels of this (although not necessarily use it at every level):

void handleFoo() {
    ErrorMessage msg("Handling foo event");
    someOperation();
    msg.done();
}

And add more constructors and members:

void handleBar(const BarEvent &b) {
    ErrorMessage msg(std::stringstream("Handling bar event ") << b.id);
    someOperation();
    msg.done();
}

And you needn't write the messages to std::cout. It could be to some logging object, and the object could queue them, and not actually output them to the log unless the catch site tells it to. That way, if you catch an exception that doesn't warrant logging, nothing is written.

It's not pretty, but it's prettier than try/catch/throw or checking return values. And if you forget to call done on success (for example if your function has multiple returns and you miss one), then you will at least see your mistake in the logs, unlike a resource leak.

[Edit: oh, and with a suitable macro you can store __FILE__ and __LINE__ in the ErrorMessage.]

Steve Jessop
One potential alternative to explicitly calling `done()` is to have the destructor call `uncaught_exception()` and only print (or log or whatever) the message if there is an active exception. While I know the experts say "it's almost never a good idea to use `uncaught_exception()`," I think this particular case might be okay because it's one of the few times that you really do want different behavior depending on whether the destructor was called as a result of normal end-of-scope or due to stack unwinding. At least, I did that in a hobby project and didn't run into any trouble with it.
James McNellis
@James: wow. You might be right. I'm stunned :-)
Steve Jessop
@Steve: I too am usually pretty stunned when I might be right. :-P
James McNellis
@James: After reviewing the relevant GOTW, the case where checking `uncaught_exception` in the destructor goes wrong, is when some other destructor calls code with an ErrorMessage in it (for example a `flush` function might reasonably want to log on error), suitably surrounded by try/catch, and then that destructor is called during stack unwinding. The error message in the `flush` function will be printed even if the `flush` didn't throw an exception. I *think* this can be fixed by checking `uncaught_exception` in the constructor too, but I'm not sure.
Steve Jessop
@James my initial excitement was curbed by http://www.gotw.ca/gotw/047.htm.
Alex B
That's "fixed" in the sense that we suppress the error message regarding `flush`, on the basis that we already have an exception to worry about. It's still not perfect, since it might be useful for the log to show that the flush failed as well as the first thing failing. But we know that if `uncaught_exception` is true when we create our ErrorMessage, then any exceptions thrown will be suppressed. So suppress the message too, why not? Amusingly, Sutter might have had an ulterior motive in declaring `uncaught_exception` immoral: http://world.std.com/~swmcd/steven/ms/bugs.html
Steve Jessop
Ha ha; that GOTW was written before he joined the VC++ team at Microsoft :-) (and, VC++ actually supports it correctly now anyway). When I used it, it was for a much more limited purpose and no constructor or destructor was going to do anything that would create an `ErrorMessage`. I don't really like the "just suppress messages from exceptions thrown in a dtor" idea... you might actually want the information where you handle the exception since you can handle it before the dtor returns. Another option would be to use an ExceptionHandler object to keep track of active exceptions.
James McNellis
(I admit, when I used `uncaught_exception()` for this, I wasn't using it for general exception handling; it was only being used for reporting diagnostic messages from a compiler; making it work in the general case may be a lot more work; I'll have to dig it up and play with it)
James McNellis
+2  A: 

Say a very generic function fails, the stack is unwound to the top level and all I see in the logs would be [...]
What's a better way for detailed for error reporting in standard C++?

Error handling isn't local to a class or library - it is a application level concern.

Best I can answer you question is that the error reporting should be always implemented by looking first and foremost at the error handling. (And the error handling also including the handling of the error by the user.) Error handling is the decision making about what has to be done about the error.

That is one of the reasons why error reporting is an application level concern and strongly depends on the application workflow. In one application the "connection reset by peer" is a fatal error - in another it is a norm of life, error should be silently handled, connection should be reestablished and pending operation retried.

Thus the approach you mention - catch the exception, construct a new one with more detailed context information and re-throw it - is a valid one too: it is up to the top level application logic (or even user configuration) to decide whether the error is really an error or some special (re)action has to taken upon the condition.

What you have encountered is one of the weaknesses of so called out-of-line error handling (aka exceptions). And I'm not aware of any way to do it better. Exceptions create a secondary code path in the application and if error reporting is vital the design of the secondary code path has to treated just like the main code path.

Obvious alternative to the out-of-line error handling is the in-line error handling - good ol' return codes and log messages on the error conditions. That allows for a trick where application saves all low-severity log messages into internal (circular) buffer (of fixed or configurable size) and dumps them into the log only if high-severity error happens. This way more context information is available and different layers of application do not have to be actively aware of each other. That is also standard (and sometimes literally "standard" - mandated by law) way of error reporting in application fields like safety and mission critical software, were one is not allowed to miss errors.

Dummy00001
A: 
Nick
Unfortunately I am using Boost and STL, which do throw exceptions. So exception-less code doesn't play well with it.
Alex B
Yeah, no great solutions there... you usually end up wrapping STL/boost calls with try/catch blocks anyway. Still prefer my solution in general for my code/functions, and just have higher-level handlers for unexpected library exceptions, but mixing the paradigms can get messy.
Nick
+1  A: 

You could add a call stack to your exception. I'm not sure how good this works for release builds but works like a charm with debug. You could do this in the constructor of your exception (to encapsulate it). See here for a starting point. This is possible as well on Linux - eventhough I dont remember how exactly.

David Feurle
A: 

I use RAII and exceptions and just have various unit-test-like assert statements throughout the code - if they fail, the the stack unwinds to where I can catch and handle them.

#define APP_ASSERT_MSG(Class,Assertion,szDescription)   \
    if ( !(Assertion) )                                \
    {                                                  \
        throw Class(__LINE__,__FILE__,szDescription);  \
    }

For most of my particular use case, all I care about is logging debug information, so my exception has the file and line number in it along with an error message (message is optional as I have an assert without it as well). You could easily add FUNCTION or an error code of some type for better handling.

I can then use it like this:

int nRet = download_file(...);
APP_ASSERT_MSG(DownloadException == ERR_OK, "Download failed");

This makes error handling and reporting much easier.

For really nasty debugging, I used GCC's function instrumentation to keep a trace list of what's going on. It works well, but slows down the app quite a bit.

Eric Holmberg
+2  A: 

As James McNellis suggested here, there is a really neat trick involving a guard object and the std::uncaught_exception facility.

The idea is to write code like this:

void function(int a, int b)
{
  STACK_TRACE("function") << "a: " << a << ", b: " << b;

  // do anything

}

And have the message logged only in case an exception is actually thrown.

The class is very simple:

class StackTrace: boost::noncopyable // doesn't make sense to copy it
{
public:
  StackTrace(): mStream() {}

  ~StackTrace()
  {
    if (std::uncaught_exception())
    {
      std::cout << mStream.str() << '\n';
    }
  }

  std::ostream& set(char const* function, char const* file, unsigned int line)
  {
    return mStream << file << "#" << line << " - " << function << " - ";
  }

private:
  std::ostringstream mStream;
};

#define STACK_TRACE(func)                           \
  StackTrace ReallyUnwieldyName;                    \
  ReallyUnwieldyName.set(func, __FILE__, __LINE__)

One can use __PRETTY_FUNC__ or equivalent to avoid naming the function, but I have found in practice that it was too mangled / verbose for my own taste.

Note that you need a named object if you wish it to live till the end of the scope, which is the purpose here. We could think of tricky ways to generate a unique identifier, but I have never needed it, even when protecting narrower scope within a function, the rules of name hiding play in our favor.

If you combine this with an ExceptionManager (something where exceptions thrown register themselves), then you can get a reference to the latest exception and in case of logging you can rather decide to setup your stack within the exception itself. So that it's printed by what and ignored if the exception is discarded.

This is a matter of taste.

Note that in the presence of ExceptionManager you must remain aware that not all exceptions can be retrieved with it --> only the ones you have crafted yourself. As such you still need a measure of protection against std::out_of_range and 3rd party exceptions.

Matthieu M.
Martin
On second thought, the problem seems to be that `uncaught_exception` does not tell you which exception is uncaught and therefore it might be a pretty bad idea to use this in case `std::bad_alloc` is active, which you cannot detect.
Martin
Matthieu M.
As in the comments to my answer, note that `uncaught_exception` *does not* tell you whether an exception was thrown from within the scope of your `StackTrace` object. It tells you whether there is an exception active, which is a necessary but not a sufficient condition to imply that a message should be printed. This code can produce spurious error messages. I've never used ExceptionManager, so if it solves this problem I'm keen to see how.
Steve Jessop
@Steve: the idea of the ExceptionManager is that user exceptions will be "registered" in the active exception slot when thrown (typically because they share a common base class). Then, instead of logging, you add the various traces to the exception itself, which will only be printed through the `what`. Therefore if the exception is ignored, nothing is printed. What I don't like about it is that if the exception is ignored nothing is printed... of course :) I much prefer systemic tracking of stack unwinding, too many lazy `catch(...)` around for my taste.
Matthieu M.