Topic: Conflicting purposes of exception spec


Author: "Paul D. DeRocco" <pderocco@ix.netcom.com>
Date: 1996/02/18
Raw View
It occurs to me that exception specifications, at least empty ones,
have two possible purposes, and that they conflict in a sense. One is
to inform the compiler that exceptions actually won't occur, and the
other is to trap any exceptions that the programmer didn't think would
occur but wasn't sure about. The former is in the category of an
optimization hint, like the "register" keyword, while the latter is a
debugging aid, like an assertion.

An empty exception specifier can be a significant aid to optimizing
the caller of a function. If the caller has a local object whose class
has a destructor, it needs to set up an exception context to guarantee
that the destructor is called even if an exception is thrown. If the
body of the function contains no function calls and no throw
statements, then it can optimize out the exception context. In theory,
it can also optimize it out when there are function calls, as long as
they all have throw() in their declarations.

So consider a simple function that I _know_ won't throw an exception:

    int sumsq(int a, int b) { return a * a + b * b; }

Do I put a throw() clause on it or not? If the compiler uses throw()
only as an optimization hint to the caller, then I most definitely
_do_ want to use it. If, on the other hand, the compiler treats
throw() as a command to add code to prevent any exceptions from
propagating out of the function, then I most definitely _don't_ want
to use it. If I guess wrong about the interpretation, I've bought
myself nothing, and cost myself something. And unless it's spelled out
in the language definition, I can't make _either_ choice without
knowing that it won't ever be compiled with a compiler that was
designed with the opposite choice in mind.

When non-empty exception specifiers are present, things are a bit
simpler, because such a construct isn't particularly useful as an
optimization hint. When compiling the caller of such a function, an
exception context is needed to guarantee destructor cleanup if _any_
exception can be thrown, even if it is a limited set. About the only
situation where a non-empty exception specifier can aid optimization
is if a try block contains only calls to functions that can throw
exceptions that aren't mentioned in the catch clauses. But in real
life, one doesn't try to catch exceptions unless they can actually
occur.

This suggests that the use of an exception specifier as an assertion
is the "correct" interpretation. But the use of an empty specifier as
an optimization hint is potentially useful, so I propose an
alternative. Let throw() intercept all exceptions and turn them into
unexpected(), but use throw(void) to mean that the function really
doesn't throw any exceptions.

So what should happen if a function declared with throw(void) actually
throws an exception? Consider it "ill-formed", and let it do whatever
it likes, including unwinding the stack and missing destructors. This
would certainly be a bug, but there are entire categories of functions
that the programmer can be assured will never throw exceptions,
including just about the entire C string library.

--

Ciao,
Paul D. DeRocco

[ To submit articles: Try just posting with your newsreader.  If that fails,
        use mailto:std-c++@ncar.ucar.edu
  FAQ:    http://reality.sgi.com/employees/austern_mti/std-c++/faq.html
  Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: "Paul D. DeRocco" <pderocco@ix.netcom.com>
Date: 1996/02/19
Raw View
Steve Clamage wrote:

> If sumsq has a throw() clause, the compiler can assume that
> no exceptions will escape from sumsq, and perhaps can avoid adding
> exception-handling code to the calling location. At the call
> site, there are no disadvantages to sumsq's empty exception-spec,
> and there are potential advantages, as you noted in your article.

The Borland compiler makes no attempt at optimizition of the caller in
this case.

> If a function has an empty exception-spec, the compiler must in
> general add scaffolding to the function to ensure that any exceptions
> occurring in the function do not escape. In the case of sumsq, the
> compiler may be able to determine that no exception is possible,
> and not generate the extra code. If the extra code is generated,
> on most implementations it does not add to the run time of the
> function.  Typically none of the extra code is executed unless an
> exception actually occurs and would otherwise escape from the
> function.

The Borland compiler makes no attempt to deduce whether it really needs
this code, and adds it no matter what. It amounts to a couple of calls
to helper functions which don't amount to that much code, but too much,
in my view, for simple functions like sumsq (or, say, C string handling
functions like strlen).

It was my experience with the Borland compiler that led me to these
thoughts. I tried adding throw() to all my diddling little utility
functions, in the hopes that it would simplify some of the calling code,
and was surprised to find that it didn't do that, but instead made the
functions more complicated. I then decided to junk the throw()
specifiers. Then, however, it occurred to me that with a different
compiler, junking the throw() specifiers could be the exact wrong thing
to do. Hence, my desire for throw(void) to be explicitly defined as an
optimization hint.

--

Ciao,
Paul D. DeRocco

[ To submit articles: Try just posting with your newsreader.  If that fails,
        use mailto:std-c++@ncar.ucar.edu
  FAQ:    http://reality.sgi.com/employees/austern_mti/std-c++/faq.html
  Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]