easy exceptions

C++ exceptions are a distraction. They foil the debugger and they diffuse our attention from the predominant control flow of the software. At point where we throw these exceptions, we usually don’t have enough context to distinguish static programming bugs from dynamic errors. Since our tools aren’t quite as handy with relating exceptions to the underlying programming bugs, the exception seems to get in the way of the debugging effort. We can only see a slice of the exception object. The (Microsoft) debugger is inconsistent about where it breaks when an exception is thrown (for caught versus uncaught exception).

Last week, another developer and I spent a total of four developer-hours hunting down a problem. A couple of exceptions were being thrown. As it turns out, I had mistyped a directory name, which caused boost::filesystem to emit an error in my graphics thread. This in turn caused some collateral damage to a communication process in a different thread - perhaps a timeout due to some interference from the debugger. The debugger tripped on the communication thread before the filesystem error. We noticed the exception, but the filename did not appear in the “what” text of the filesystem error. So, the code-review concentrated on the more complicated communication thread first, wasting a lot of time. If only the filesystem library could have noted the filename in the “what” text, then we could have avoided the extra work.

This little wrong-turn led me to look for a better way of organizing exceptions. C++ exceptions protect our routines from violating their postconditions. They give a routine the chance to “give up” - to surrender the responsibility of coping with a particular input. Of course, for the developer, exceptions are a relatively poor debugging tool. The problem is that they only carry a small fraction of the information that the debugger would be able to provide. Even when the exception carries the appropriate information, it can be difficult to access. For example, my filesystem exception carried a filename field, but the filename did not carry through to the
“what” message. At this early stage of the project, the “what” text is dumped to a debug window, but there are not yet specializations for the many types of exceptions that could wind up falling into my error handler.

An exception object really has two significant responsibilities:

  1. Pass sufficient information about the context of the error back to the exception handler so that it can retry or a useful message can be sent to the user.
  2. Make enough information available to the developer to debug unanticipated exceptions.

We are all used to dealing with the first point. Since arbitrary data can be carried by the exception object, it is easy to meet the requirement. My troublesome filesystem error certainly contained everything that the user needed to know. However, the second point is quite open-ended. We surely do not want to embed a fully featured debugger in our exception handler. On the other hand, it would sure be nice if a whole lot of the context of the exception were carried.

One way to address the issue is to make an exception object that is trivial to add information to, and everything is exposed through the “what” message. For example,

 throw error() << boost::error_info<filename_tag>(path);

There are a couple of problems with the approach. First, the identity of the fields inserted to the stringstream are lost. When my application is all grown up, a filesystem exception will cause a dialog box to open or trigger a search process. In either case, the path in question will need to be interpreted, and extracting it from the human-readable string is going to be annoying.

Second, the stringstream copy constructor and insertion operators can throw bad_alloc. If the copy constructor throws while the exception is being propagated (and not caught before the end of the function), then terminate() will be called. If the insertion operator throws, then the original exception will be lost. David Abrahams warns against embedding std::string objects or formatting before the call to what() for these reasons.

Here is an abbreviation of David Abrahams’s guidelines for exception classes for the boost library:

  1. Derive your exception class from std::exception.
  2. Use virtual inheritance.
  3. Don’t embed a std::string object.
  4. Format the what() message on demand.
  5. Don’t worry too much about the what() message.
  6. Expose relevant information about the cause of the error.
  7. Make your exception class immune to double-destruction.

Items 1 and 2 are addressed with some trivial syntax (make the stringstream a member of a class that inherits from std::exception). It’s worth noting that we are attacking the basis of item 5; in fact our goal is to get enough information in the basic exception object to reduce the need for the debugger. Item 7 can probably be solved using a custom allocator.

Items 3, 4 and 6 are non-trivial to address. There is no built-in way to delay the formatting of an arbitrary list of stream insertion operations. More importantly, there is no easy way to identify fields within the formatted string. The complexity of any attempt to delimit fields in the text string seems to be out of line with the relatively small scale of the problem.

Emil Dotchevski has proposed a library for boost that addresses the basic problem of easily recording arbitrary data fields in an exception object. Here is a before and after example.

Standard exception

 struct error : std::exception {   setFilename(const filesystem::path&);   const filesystem::path& getFilename() const;   const char* what() const; }; void f(boost::filesystem::path path) {   error e;   e.setFilename(path);   throw e; }

With Dotchesvski’s boost Exception proposal:

 struct filename_tag : error_info_value {}; struct error : boost::exception {}; void f(boost::filesystem::path path) {   throw error() << boost::error_info<filename_tag>(path); }

The library is successful at eliminating boilerplate implementation of what and getters/setters. It is well suited to situations where errors can be complex. For example, OpenGL can report a vector of error flags, and there is even the possibility of a flag having multiplicity greater than one. While it is possible to build this field into the type of the exception, it appears to be counterproductive. Microsoft Visual Studio 8 usually describes such a complicated exceptions as having an “unknown type” (although the correct catch clause does get invoked). OpenGL error flags could be easily encoded in the fields of Dotchevski’s library.

Nevertheless, our desire is to make it easy for the library designer to expose the widest variety of details of the context of the error. We expect a wider variety of details to be relevant for debugging than would be required to determine messages for the end-user. While Dotchevski eliminated the boilerplate code that would normally be required for transmitting predefined fields, he does not let the programmer easily add ad-hoc bits of context.

Some people would prefer to use the “what” message as a key to the exception type. The string could serve as a key to a table of user error messages or otherwise provide the fine-grained choice of action. The catch stage of exception filtering is then a more coarse filter. However the typeid operator can be employed for the same purpose, freeing the “what” message for use as a debug stream.

I developed my own exception class to make it as easy as possible to add data to the exception object. The source code is available here. It uses an ostream syntax that can accept any object that is copyable and has a ostream inserter. Here is the basic interface:

 class exception { public:   virtual const char* what() const throw();   void visit(const error_info_visitor_base& v) const;   template void set(const T&t) const;   virtual ~exception() throw(); }; template<typename E, typename F> const E& operator<< (const E& e,const T& t) {   e.set(t);   return e; }

It is the same as std::exception, with the addition of set and visit member functions and an insertion operator. The set member function adds an object to a list of data elements. The visit member function applies a visitor to each data element whose type is compatible with the visitor. To help the error handler get data out of the exception object, there are helper functions for constructing an error_info_visitor class:

 unspecified_type on_error_info(Tag, F f) {   return error_info_field_visitor<error_info&ltTag>,F>(f); } unspecified_type on_error_info(F f) {   return field_visitor<F>(f); }

Here is an example where an exception is thrown, and a data element is added and extracted.

 try {   throw exception() << 5 << "Hello World"; } catch(exception&e) {   using boost::lambda::_1;   using boost::lambda::var;   int i(0);   e.visit(boost::on_error_info<int>(var(i)=_1));   cout << i << "\n"; /*prints "5"*/   cout << e.what() << "\n"; /*prints: "5Hello World"*/ }

At the throw site, the data values are simply recorded. String formatting is not done until the what member function is called.

Often, we need to “translate” exception types in order to better describe the context of the error. My exception class allows the data elements from the first to be inserted into the second exception object. Here is an example:

 class system_error : public boost::exception {   int errno_; public:   explicit system_error(int err=0) : errno_(err) {}   int& err() {return errno_;} }; void f() {   try {     try {       throw boost::exception() << 9 << " hello ";     }     catch(boost::exception&e) {     throw system_error(2) << e;   } } catch(system_error&e) {   using boost::lambda::_1;   using boost::lambda::var;   int i(0);   e.visit(boost::on_error_info&ltint>(var(i)=_1));   cout << i << '\n'; /*prints '9'*/   cout << e.what() << '\n'; /*Prints "9 hello world"*/   cout << e.err() << '\n'; /*Prints '2'*/ }

In the next post, I’ll show how to emulate part of the new C++0x support for “saving exceptions”.

Leave a Reply