Handling Exceptions, Part 11

Robert Schmidt

November 18, 1999

Part 11 of this series on exception handling discusses exception specifications.

It's time now to explore the C++ Standard Library's exception support as declared in the header <exception>, and the Visual C++® implementation of same. As described in the C++ Standard (subclause 18.6, "Exception handling"), this header declares:

I'll start by analyzing exception specifications and what terrors await when your program violates them. That analysis should pull in all of the topics listed above, and a few loose ends related to C++ exception handling in general.

Exception Specification Recap

Exception specifications are grammatically part of a C++ function declarator; they specify which exception(s) that function may throw. For example, the function

void f1() throw(int)

may throw an int exception, while

void f2() throw(char *, E)

may throw a char * and/or an E (where E is some user-defined type) exception. An empty specification

void f3() throw()

implies a function will throw no exceptions, while a missing specification

void f4()

implies a function may throw anything. Note that the syntax

void f4() throw(...)

would have been more consistent for a "throws anything" function, since it mimics the "catch anything"

catch(...)

However, the accepted grammar correctly allows older functions written before exception specifications existed.

Violating Specifications

So far, I've written that functions may throw exceptions described in their specifications. "May" is a bit gauzy; "must" would inspire more confidence. "May" suggests that functions are allowed to ignore specifications at their whim. You'd like to think that translators would prevent such audacity:

void f() throw() // Promises not to throw...
   {
   throw 1;      // ...but does anyway - error?
   }

You'd like to think that, but you'd be wrong. Feed the above code snippet to Visual C++. You'll find the compiler's resulting silence deafening -- it finds no translation-time errors in this function. In fact, of all the systems with which I test, not one finds a translation-time error here.

Nonetheless, exception specifications do have the rule of law behind them, and functions violating their specifications do suffer real consequences. Unfortunately, those consequences manifest at run time instead of at translation time. To see this, incorporate the above snippet into a small complete program:

void f() throw()
   {
   throw 1;
   }

int main()
   {
   f();
   return 0;
   }

What do you think will happen when this program runs? Well, f throws an int exception, in violation of its contract. You might think that the exception will percolate beyond main into the run-time library ether surrounding your program. Based on that assumption, you could be inclined to apply a simple try block:

#include <stdio.h>

void f() throw()
   {
   throw 1;
   }

int main()
   {
   try
      {
      f();
      }
   catch (int)
      {
      printf("caught int\n");
      }
   return 0;
   }

to catch the exception before it leaks beyond main.

Indeed, if you build and run the above program with Visual C++ version 6.0, you are rewarded with

caught int

Seemingly vindicated, you wonder anew what possible good the throw() specification has done, other than to increase your source code size, and thus your apparent productivity in the eyes of management. Your wondering slows once you recall previous Visual C++ deviations from the C++ Standard, and finally detours to a new question: Does Visual C++ properly handle violations in exception specifications?

Survey Says ...

No.

Were this program's behavior Standard-conformant, the catch clause would never be entered. From the Standard (subclauses 15.5.2 and 18.6.2.2):

[A]n exception-specification guarantees that only the listed exceptions will be thrown.

If a function with an exception-specification throws an exception that is not listed in the exception-specification, the function

void unexpected();

is called immediately after completing the stack unwinding for the former function.

The unexpected() function shall not return ...

[A] handler function [is] called by unexpected() when a function attempts to throw an exception not listed in its exception-specification. The implementation's default [handler function] calls terminate() [to end the program].

After I give you a brief unexpected tutorial and example, I'll show how Visual C++'s behavior deviates from the Standard.

unexpected Primer

unexpected is a Standard Library function declared in header <exception>. Similar to most Library entities, unexpected lives within namespace std. It takes no arguments, and returns no values; indeed, unexpected never returns, much as abort and exit never return. If a function violates its exception specification, unexpected is called immediately after the offending function's stack unwinds.

Based on my interpretation of the Standard, the Library's unexpected implementation is conceptually

void _default_unexpected_handler_()
   {
   std::terminate();
   }

std::unexpected_handler _unexpected_handler =
      _default_unexpected_handler;

void unexpected()
   {
   _unexpected_handler();
   }

(_default_unexpected_handler and _unexpected_handler are my own convenient fiction. Your Library implementation may name them something else, or use an entirely different internal implementation.)

std::unexpected calls a function to actually handle the unexpected exception. It references this handler function through a hidden pointer (_unexpected_handler) of type std::unexpected_handler. The Library provides a default handler (_default_unexpected_handler), which calls std::terminate to end the program.

As the indirection through the pointer _unexpected_handler suggests, you can replace the internal call to _default_unexpected_handler with a call to your own handler. That handler's address must be compatible with the pointer type std::unexpected_handler:

typedef void (*unexpected_handler)();

Also, a handler must not return to its caller (std::unexpected). Nothing prevents you from writing a handler that returns; however, such a handler is not Standard-conformant, and the resulting program behavior is ill-defined.

You register a replacement handler via the Standard Library function std::set_unexpected. Note that the Library maintains only one handler for all unexpected exceptions; once you call set_unexpected, the Library no longer remembers the previous handler. (Compare this to atexit, which accumulates at least 32 exit handlers.) To overcome this limitation, you can either set different handlers at different times, or instrument your handler to behave differently depending on program context.

Visual C++ vs. unexpected

Try out this simple example:

#include <exception>
#include <stdio.h>
#include <stdlib.h>
using namespace std;

void my_unexpected_handler()
   {
   printf("in unexpected handler\n");
   abort();
   }

void throw_unexpected_exception() throw(int)
   {
   throw 1L; // violates specification
   }

int main()
   {
   set_unexpected(my_unexpected_handler);
   throw_unexpected_exception();
   printf("this line should never appear\n");
   return 0;
   }

When built and run on a Standard-conforming implementation, this program produces

in unexpected handler

possibly followed by a message about abnormal program termination (thanks to the abort call). But when built and run with Visual C++, this same program throws up an "Unhandled exception" dialog box. Once you dismiss the dialog, the program produces

this line should never appear

Sad to say, Visual C++ does not correctly implement unexpected. The function is declared in <exception>, and the run-time library has an implementation for it. It's just that the implementation doesn't appear to do anything useful.

Actually, Visual C++ doesn't even get the declarations right, as demonstrated by the theoretically equivalent program

#include <exception>
#include <stdio.h>
#include <stdlib.h>
using namespace std;

void my_unexpected_handler()
   {
   printf("in unexpected handler\n");
   abort();
   }

void throw_unexpected_exception() throw(int)
   {
   throw 1L; // violates specification
   }
int main()
   {
   std::set_unexpected(my_unexpected_handler);
<  throw_unexpected_exception();
   printf("this line should never appear\n");
   return 0;
   }

Visual C++ won't compile this program. Inspection of <exception> reveals the problem: set_unexpected_handler is incorrectly declared at global scope instead of within namespace std. In fact, all of the unexpected family are declared at global scope.

Bottom line: Visual C++ will translate programs using unexpected et al.; however, the ensuing run-time behavior will be incorrect.

I'm hoping that Microsoft will correct these problems in its next compiler version. Meanwhile, for the rest of my discussion of unexpected, I'll assume that you are using a Standard-conforming C++ translator.

Keeping Programs Alive

In the simple example I've shown, the program halts within my_unexpected_handler. Sometimes having the program halt is desirable and correct. Most of the time, however, a program halt is too severe a response, especially when the unexpected exception betrays a mild program anomaly or simple programmer error.

Let's assume that you want to handle an unexpected exception, then resume the program, much as you would respond to most other "normal" or expected exceptions. Given that unexpected never returns, program resumption seems impossible -- until you read in Standard subclause 15.5.2:

The unexpected() function shall not return, but it can throw (or re-throw) an exception. If it throws a new exception, which is allowed by the exception specification that previously was violated, then the search for another handler will continue at the call of the function whose exception specification was violated.

Winner! If my_unexpected_handler throws an allowed exception, the program can resume after the point of the original violation. In our example, the original exception specification allows exceptions of type int. By implication then, if my_unexpected_handler throws an int exception, the program should be able to continue.

In light of this surmising, try

#include <exception>
#include <stdio.h>

void my_unexpected_handler()
   {
   printf("in unexpected handler\n");
   throw 2; // allowed by original specification
   abort();
   }

void throw_unexpected_exception() throw(int)
   {
   throw 1L; // violates specification
   }

int main()
   {
   std::set_unexpected(my_unexpected_handler);
   try
      {
      throw_unexpected_exception();
      printf("this line should never appear\n");
      }
   catch (int)
      {
      printf("program resumed\n");
      }
   return 0;
   }

When run in a Standard-conforming environment, the program produces

in unexpected handler
program resumed

as desired.

The thrown int exception percolates up the call chain like any other exception, to be caught by the first compatible handler (catch clause) along the way. In our example, program control unwinds from my_unexpected_handler to std::unexpected to main, where the exception is caught. When used this way, my_unexpected_handler becomes an exception filter, effectively translating the original "bad" long exception into a "good" int exception.

Moral: By filtering an unexpected exception, you can translate it into an expected exception and allow your program to resume execution.

Segue

Next time, I'll finish the discussion of std::unexpected by showing limitations in the exception-throwing my_unexpected_handler, exploring Library remedies for those limitations, and giving general guidelines for managing unexpected exceptions. I'll also start discussing the related Library function std::terminate.

Robert Schmidt is a technical writer for MSDN. His other major writing distraction is the C/C++ Users Journal, for which he is a contributing editor and columnist. In previous career incarnations he's been a radio DJ, wild-animal curator, astronomer, pool-hall operator, private investigator, newspaper carrier, and college tutor.

Deep C++ Glossary