TITLE: C++ puzzle: throw and exceptions

[ This material taken from "C++ Report", vol. 6, num. 9, p. 64 ]


extern void unknown ();

void f (int) throw ()
{
	unknown ();
}

void g ()
{
	try
	{
		f ();
	}
	catch (...)
	{
		// Can control ever get here?
	}
}

The declaration of f has an empty throw specification; this promises that f
will never throw an exception. So we might be tempted to think that the
flow of control will never enter the catch block. But there is a loophole
(according to the July 1994 ISO/ANSI draft).

Suppose the unknown() function throws an exception. When the stack gets
unrolled to f(), the "unexpected" function calls terminate(). However,
the user can override this default by calling set_unexpected:

	extern void my_unexpected ();

	main ()
	{
		set_unexpected (my_unexpected);
		// ...
	}

The ISO/ANSI draft (section 15) places the following constraint on 
my_unexpected():

	The unexpected() function shall not return, but if can
	throw (or re-throw) an exception. Handlers for this
	exception will be looked for starting at the call of
	the function whose exception-specification was
	violated. Thus an exception-specification does not
	guarantee that only the listed classes will be thrown.

This means we can implement my_unexpected as:

	void my_unexpected ()
	{
		throw 1;
	}

Now let's go back to our original example and see what happens
if unknown throws an exception:

	1. The stack gets unwound to the call of f.

	2. Since f "promises" not to throw an exception, the
	   "unexpected" function (my_unexpected) is called.

	3. my_unexpected throws a new exception.

	4. The search for a catch block begins at the 
	   call to f.

	5. Control is transferred into the catch block in g!

The moral: an exception specification does not guarantee that any
exceptions will or will not be thrown from within a function. It
only guarantees that the "unexpected" function will be called,
and that this function will either end the program or throw an
exception. This is a pretty weak guarantee, especially since 
unknown might have called set_unexpected just before throwing the
exception.
