Exceptions are a necessary part of the C++ language, but for most programmers they are worse than worthless - they are unusable. When exceptions were first added to the language back in the days before standardization, they were seen as a brilliant improvement over the hideous setjmp/longjmp facility from ANSI C. Because exceptions unwind the stack when they are thrown, they call destructors and clean up all of your untidy messes as they go.
Tom Cargill |
It didn't take long for somebody to notice that the emperor had no clothes. In 1994, Tom Cargill published a prescient article in the late C++ Report. Tom pointed out in detail just how difficult it was to write code that actually held up when exceptions were used. Scott Meyers flags this as one of his five Most Important C++ Non-Book Publications...Ever. (Incidentally, Tom is responsible for a piece of wisdom that sounds like a an offhand quip: the ninety-ninety rule. Any project manager who doesn't understand this rule all the way into his or her bones needs to quit. Now.)
The intervening years have not been kind to exceptions. The problems Tom foresaw in 1994 were real, and while they can be managed, writing exception-safe code is still fraught with peril. As a result, exceptions are basically unused by by most C++ programmers. After all, we have to deal with tough problems like writing safe multithreaded apps, understanding template metaprogramming, and dealing with the language's high-maintenance memory management. The last thing we need is to make use of a feature, that while useful, is basically impossible to use correctly.
Naturally, after this rather verbose introduction, you have probably guessed that I'm using this article to tell you about a simple little utility class I use to help me throw informative, easy to build exceptions in my C++ programs. Yes, exceptions are problematic, but there's one place I can use them with impunity: to cause a fatal error that aborts my program.
Fatal Errors Considered Exceptional
If you analyze Tom Cargill's article, or look into any of the additional work that has been done on exceptions up until today, you'll see that most of the nasty side effects are only a problem for a program that tries to keep running in a predictable and safe fashion while throwing and catching exceptions.
The side effects of code that isn't quite exception-safe include memory leaks, partially constructed objects, and invalid containers. All bad things. But the one place they usually don't matter to me is when things have degraded in my program to the point where I'm ready to throw in the towel anyway. That point is when I've encountered a fatal error and am aborting the program. At that point, I throw an exception, which generally percolates all the way up to main(), where it is caught, and error message is printed, and the program exits.
Handling fatal errors this way makes for much cleaner code. I generally assume that every method or function called in my program succeeds, and don't worry about creating special returns with error codes. I blithely march through call after call without checking results, secure in the knowledge that my code is working properly. I know this is the case, because if something went wrong, an exception would have been thrown, and my program would abort.
This error handling strategy is implemented entirely in main(), which generally follows this form in one of my programs:
-
int main( int argc, char *argv[] )
-
{
-
try {
-
//
-
// all the work is enclosed in this try block
-
//
-
...
-
}
-
catch ( const std::exception & e )
-
std::cout <<"\nFatal error: " <<e.what() <<"\n\n";
-
std::cout <<"Hit enter to continue...";
-
std::string temp;
-
std::getline( std::cin, temp );
-
return -1;
-
}
-
return 0;
-
}
There are a couple of interesting points to note about the error handler you see here. First, because I am catching std::exception, I will catch any errors in the standard library, such as bad_alloc, logic_error, runtime_error, etc. It's a good idea to have a top-level try/catch block for these items anyway, so my use of exceptions for fatal errors forces good hygiene practices.
Second, I'm using the what() method to give human-readable feedback on exactly what happened to send my program off the rails. This method is defined as virtual for the base class std::exception, so all derived classes will support it. There's no requirement that they populate this string with great prose, but we can at least expect implementors to provide something informative here.
My Exception Class
When I first started using exceptions for fatal errors, I kept things simple by just throwing instances of std::runtime_error when I wanted to abort my program. I could pass the constructor of this object a descriptive string as it was constructed, and that same string would be returned when what() was called in the exception handler. Typical usage might look like this:
-
#include <stdexcept>
-
-
int main( int argc, char *argv[] )
-
{
-
try {
-
if ( argc <2 )
-
throw std::runtime_error(
-
"Usage:\n\n"
-
"add_link_error url\n" );
This code ensures that if the user leaves off the necessary command line argument, the catch() block will print out a usage statement and return an error value to the invoking shell, which is just what we want. I manage to handle faulty input with just one line of code, don't have to set any error variables, no if/then/break clause to print an error and then exit. It's tidy and works well.
Of course, it wasn't long until I ran into situations where I wanted to provide the user a little more information with a fatal error - information that had to be formatted at runtime. I ended up writing a lot of code that looked like this:
-
FILE *fp = fopen( "database.txt",
-
"r" );
-
if ( !fp ) {
-
std::stringstream s;
-
s <<"Error opening file, errno value of "
-
<<errno
-
<<" translates to "
-
<<strerror( errno );
-
throw std::runtime_error( s.str() );
-
};
This more or less worked, but it added a lot of lines to the code, and as a rule, the fewer lines, the less typing, the more I like it. In this case I have to construct a separate std::stringstream object to hold the formatted error message (or a character buffer if I choose to go old school and use vnsprintf() , a second line to do the formatting, and a third line to construct and throw the exception.
In addition to writing three lines instead of one, I now have to enclose the whole thing in brackets, because the clause following the if statement is multiple lines, which makes the whole thing require five lines of code instead of one!
A Better Way
I needed to find a better way to do this. I first dabbled with a class derived from std::exception that used vnsprintf() to format arguments, C-style. But there were a few disadvantages to this, the primary one being that it made it hard to take advantage of classes that have their own overrides to stream classes. In other words, if I've bothered to create a stream override so I can print the contents of class foo, it's easy to write that to a stream, but not so easy to insert it into a buffer being formatted with vnsprintf().
So I determined that I wanted to have a class that that let me rewrite the code shown above so that it all fits on one line, like this:
-
FILE *fp = fopen( "database.txt",
-
"r" );
-
if ( !fp )
-
throw fatal_error() <<"Error opening file, errno value of "
-
<<errno
-
<<" translates to "
-
<<strerror( error );
My first stab at getting this to work was to create a class that used multiple inheritance to create a class that inherits from both std::exception and std::stringstream:
-
class fatal_error
-
: public std::exception
-
, public std::stringstream
-
{
-
...
With this routine I'd just need to override the definition of what() and I would be in business.
But there was a fatal flaw in this problem: the absence of a copy constructor for std::stringstream. I had assumed that the copy constructor wouldn't be needed, as I was catching the exception object by reference. My thought was that the compiler would create a temporary object, pass it along to my catch clause, and all would be well.
No such luck. In this case, the compiler has the right to make a copy, and even if it doesn't make a copy, it has the right to insist on a copy constructor even if it is only considering the possibility of making a copy. Strike one.
This One Works
So I needed to make a version of my fatal_error class that doesn't try to call the copy constructor for std::stringstream. This is accomplished easily enough by using composition instead of inheritance:
-
class fatal_error
-
: public std::exception
-
{
-
...
-
private :
-
mutable std::stringstream mStream;
-
};
This solves one problem, but creates another. Because fatal_error is no longer derived from std::stringstream, I've lost the overloaded operators that insert text into the object before it is thrown. In other words, my wished-for code shown above won't compile.
In this case, the solution is simple, I just add a template method to the class:
-
class fatal_error
-
: public std::exception
-
{
-
public :
-
template<typename T>
-
fatal_error& operator<<( const T& t )
-
{
-
mStream <<t;
-
return *this;
-
}
-
...
-
}
Now the template class takes care of routing the stream insertion output into the std::stringstream member I've incorporated into my class. Note also that the overloaded insertion operator returns a reference to the fatal_error object, allowing me to chain insertions.
Almost Done
There only two more issues to deal with in order for this class to be ready for use. First, I need to deal with the possibility of a copy constructor being called as this exception is thrown. Because I can't copy the std::stringstream object from the old object to the new, I have to save off its contents into a mutable std::string member called mWhat. The result looks like this:
-
fatal_error::fatal_error( const fatal_error &that )
-
{
-
mWhat += that.mStream.str();
-
}
Finally, I need a good version of what() that conforms to what is expected for std::exception. This is nice and easy:
-
virtual const char *fatalError::what() const throw()
-
{
-
if ( mStream.str().size() ) {
-
mWhat += mStream.str();
-
mStream.str( "" );
-
}
-
return mWhat.c_str();
-
}
Wrapped Up
With that, I have an exception that I can use to easily generate fatal error exceptions with as much data as I want. I only have to include a single header file in any code that uses the class, and I can create and throw the exception in a single line of code, which adheres to the C ideologoy of minimal typing.
Life is good.
Complete source code from the single file, fatal_error.h, is given below. This code should work in g++ 3.x code, and Visual Studio 2003 and 2005 programs. If you run into problems with different versions of various compilers, please let me know!
-
//
-
// fatal_error.h
-
//
-
#include <sstream>
-
#include <stdexcept>
-
#include <string>
-
-
//
-
// This class is designed to make it a little easier
-
// to throw informative exceptions. It's a little lame,
-
// but I do like to be able to write code like this
-
// for fatal errors:
-
//
-
// throw fatal_error() <<"Game over, "
-
// <<mHealth
-
// <<" health points!";
-
//
-
// It works everywhere I've tested it, let's hope that it holds up.
-
//
-
-
class fatal_error : public std::exception
-
{
-
public :
-
fatal_error() {};
-
//
-
// Need a copy constructor, because the runtime of the compiler is
-
// allowed to insist on it when it throws an object of this type,
-
// even if it doesn't actually make a copy. When I make a copy, I
-
// need to capture whatever is in the stringstream object. Note:
-
// in many cases, attempting to copy an iostream object leads to
-
// errors, so the copy constructor here constructs a brand new
-
// mstream object.
-
//
-
fatal_error( const fatal_error &that )
-
{
-
mWhat += that.mStream.str();
-
}
-
//
-
// Virtual dtor needed? Not really, but here it is anyway.
-
//
-
virtual ~fatal_error() throw(){};
-
//
-
// When I finally get this object to an exception handler,
-
// (hopefully catching by reference) I want to display the error
-
// message that I've inserted. To do that, I just chapture
-
// whatever is in the mWhat string object concatenated
-
// with anything that might be in the stringstring mStream object,
-
// and return it. (Odds are that only one of them will contain
-
// anything, depending on whether or not the copy constructor
-
// was called.
-
//
-
virtual const char *what() const throw()
-
{
-
if ( mStream.str().size() ) {
-
mWhat += mStream.str();
-
mStream.str( "" );
-
}
-
return mWhat.c_str();
-
}
-
//
-
// The template function used to create insertion operators for all
-
// of the various types of objects one might insert into this guy.
-
//
-
template<typename T>
-
fatal_error& operator<<( const T& t )
-
{
-
mStream <<t;
-
return *this;
-
}
-
private:
-
mutable std::stringstream mStream;
-
mutable std::string mWhat;
-
};
Tom Cargill
13 users commented in " No Exceptions - With One Exception "
Follow-up comment rss or Leave a TrackbackI am not sure but I think the recently reviewed and accepted Boost.Exception library offers equivalent functionality. You might want to check it out.
@Slavomir:
Thanks for the pointer, very timely! No, I wasn't aware of Boost.Exception, I see it was accepted into boost while I was writing this post.
It looks like the boost exception class does a nice job of encapsulating data, and naturally it does so in a very flexible way, as we would expect from the boost team.
It looks like the next boost build may be a jump from 1.3x to 2.0, perhaps that is when this library will first be available in released format.
I'll probably still continue to use my own class for little demonstration projects, such as the ones I post here. I try not to impose on my readers a requirement that they add a big distribution like the boost libraries in order to test my code if it's at all possible to avoid.
But I would definitely use the boost version in preference to my own in a real project, such as in my day job at Routers 'Я' Us.
For those interested, read the boost announcement, this temporary docs page, and the boost guidelines on error and exception handling.
Each time what() is called, a different string will be returned (because you append to mWhat each time). That seems kind of lame.
@asdf:
Yes, because I have never used this for anything except a fatal error handler I didn't pay attention to that detail - it just never came up. I've modified the code accordingly. Thanks for the note.
Does this mean that you don't consider fatal errors to be the result of bugs in your code? I realize that systems can run out of memory, have hardware problems and other unpredictable scenarios, but the vast majority of fatal errors in an application are caused by bugs in the application.
I prefer that my programs actually crash at the point of a fatal error. It allows customers to create and send crash dumps and allows me to see exactly what was happening at the point of failure. Any 'fatal condition' handling loses some data that would help in diagnosing.
@dk:
Consider this to be a replacement for assert(). If you look at the one minor example, you'll see that I throw an exception when command line arguments are missing. Not really a bug in my program, but a good reason to stop.
This kind of exception can only really be used when your program is more or less sane. It is not likely to be useful in a case where your program loses its sanity. For one thing, this is usually not something you can predict, so you aren't going to be writing conditional code to detect the condition and throw an exception.
So this is useful for things like "file not found", "unable to open network port", "database appears to have lost consistency". Not so much for bad pointer, memory leak, mismatched libraries, etc.
It's just one tool among many that you might use to handle errors in a production program.
Most projects I work on have a DEBUG_ASSERT( cond, msg, ... ) and a DEBUG_ERROR(msg, ...) macro. Some advantages: macro expansion of the line and file the error occured on - trigger a breakpoint (most debuggers can catch a line of assembly to go into the debugger) - compiles clean out.
DEBUG_ERROR( "Uh oh, if your in this branch your broke!" );
DEBUG_ASSERT( pObj, "Null pointer!" );
DEBUG_ASSERT( a == b, "a doesn't equal b cause b is %1", b );
I think you will find this method provides the benefits of your approach plus much, much more.
In languages that do not support C pre-processor style macros, I use a wrapper around throw to achieve the same thing.
@James:
I won't argue that there isn't more than one way to skin this cat - although these two approaches seem fairly equivalent to me.
A minor note: the DEBUG_ASSERT you show uses sprint() type argument formatting, and one of the reasons for this particular class was to be able to take advantage of std::ostream formatting - not that DEBUG_ASSERT couldn't have a version that does this.
As for fatal error handling in production program, DEBUG_ASSERT may or may not ultimately throw, which we hope will unwind the stack and clean up as much as possible on exit. If DEBUG_ASSERT wraps up by calling assert(), you are hosed on production.
As for picking up the __FILE__ and __LINE__, that can be done pretty easily here too - although it obviously requires invocation of the preprocessor, so your code looks like this:
[cpp]
if (something bad)
throw FATAL_ERROR
Another advantage of the macro approach is one can alter the macro if one wants different behaviour as you mention. If you want a throw instead of a hardware breakpoint you can change the define and not code spread throughout the app. A good suggestion is to change it based on the build target (e.g. hardware breakpoint in DEBUG, throw on RELEASE).
The sprintf format is just one example. I've also seen DBG_ERROR( "can you believe this: "
DBG_ASSERT( !somethingBad, "Fatal error message" )
can easily be expanded to:
if (something bad)
throw FATAL_ERROR
or to several other better implementations.
apologies - my response was mangled.
DBG_ERROR( "can you believe this: " << someString << "is not null!?" );
I prefer the sprintf format but that is still pretty clean.
The next part was just an obvious point that DBG_ASSERT could expand to your example code or several other forms. That is it's chief strength above your example.
Dear Mark:
You article "File Verification Using CRC" is dated May 1992,
but in the article you refer to Windows 98 and to Windows XP.
Is there an error in the dating of the article?
@Alan:
Ummm... try as I might I don't see any references to Windows 98 or XP in the article
http://marknelson.us/1992/05/01/file-verification-using-crc-2/
Tell me, why did you post the comment here instead of in the comments section on that article?
- Mark
I am impressed. This is what I have been trying to do this week. The trick of inserting the message into the copy of the object, bypasses the hole inheriting from a stream class that has no copy constructor.
Well done.
Leave A Reply
You can insert source code in your comment without fear of too much mangling by tagging it to use the iG:Syntax Hiliter plugin. A typical usage of this plugin will look like this:[c]
Note that tags are enclosed in square brackets, not angle brackets. Tags currently supported by this plugin are: as (ActionScript), asp, c, cpp, csharp, css, delphi, html, java, js, mysql, perl, python, ruby, smarty, sql, vb, vbnet, xml, code (Generic). If you post your comment and you aren't happy with the way it looks, I will do everything I can to edit it to your satisfaction.int main()
{
printf( "Hello, world!\n" );
return 1;
}
[/c]