Topic: Exception Specifications


Author: Morten Christensen <mor@dit.ou.dk>
Date: 1996/05/14
Raw View
Bradd W. Szonye wrote:
>
> I personally prefer the range-table implementation. Before I started
> following the net discussions, I tried to think of an implementation that
> avoids Microsoft's run-time speed penalty. I came up independently with a
> range-table method that imposes a speed penalty only in the presence of
> non-C++ functions which do not preserve frame pointers correctly. The
> trouble situation is this:
>
> A C++ function calls a non-C++ function that does not preserve the stack
> frame pointer. The non-C++ function calls a C++ callback function that
> throws an exception. The unwinding mechanism reaches the non-C++ function,
> which has basically trashed the stack by not setting up a frame properly.
>
> There are two good solutions to this: either the first C++ function does
> an explicit stack mark so the EH mechanism can skip the "bad" function, or
> the EH mechanism has no choice but to terminate().

I agree... However in order to ensure that the first solution (*) is
generally useable, it must be possible for the compiler to make such
stack marks on it own, when and only when an external (non-c++)
function may indirectly(!) throw and not handle an exception.... As it
is currently not possible to write exception specifications for extern
"C" functions then the first solution is not really feasible for the
moment (Is the C++ ISO/ANSI group working on this ???).

Readers of this thread which are interested in detailed information
about the implementation details of exception handling in
object-oriented languages (c++ etc.) which can be used by OOPL
compilers, may find my 1995 M.Sc. Thesis: "Methods for Handling of
Exceptions in Object-oriented languages" a good source. It's available
in PDF format from "http://www.cybercity.dk/users/ccc2550/archive.html".

The thesis discusses various implementation methods on various
architectures, among them a very efficient and rather unique
implementation of a zero-overhead method called the unified method
which is much like the method outlined by Bradd W. Szonye.

- Morten M. Christensen (mmc@mailhost.net)
  Odense, Denmark
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  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: "Bradd W. Szonye" <bradds@ix.netcom.com>
Date: 1996/05/14
Raw View
On Tuesday, May 14, 1996, Morten Christensen wrote...
> Bradd W. Szonye wrote:
> > [..] I came up independently with a
> > range-table method that imposes a speed penalty only in the presence
of
> > non-C++ functions which do not preserve frame pointers correctly. The
> > trouble situation is this:
> >
> > A C++ function calls a non-C++ function that does not preserve the
stack
> > frame pointer. The non-C++ function calls a C++ callback function that
> > throws an exception. The unwinding mechanism reaches the non-C++
function,
> > which has basically trashed the stack by not setting up a frame
properly.
> >
> > [..] the first C++ function does
> > an explicit stack mark so the EH mechanism can skip the "bad" function
>
> I agree... However in order to ensure that the first solution (*) is
> generally useable, it must be possible for the compiler to make such
> stack marks on it own, when and only when an external (non-c++)
> function may indirectly(!) throw and not handle an exception.... As it
> is currently not possible to write exception specifications for extern
> "C" functions then the first solution is not really feasible for the
> moment (Is the C++ ISO/ANSI group working on this ???).

Perhaps I didn't read the DWP closely enough in this area. I can still
think of two "workarounds."

+ Allow an extension where extern "C" functions may have no throw-spec (in
which case the caller marks the stack) or a throw-spec of throw() (in
which case the compiler assumes no exception will ever escape that
function). If an exception did escape a no-throw "C" function, the
behavior would be undefined, probably leading to terminate() somewhere.
I'm not sure about whether such an implementation would conform.

+ Provide two extern-specs, extern "C" and extern "callback" ... the
former would assume no exceptions escape the function, while the latter
assumes it calls back into an exception-throwing C++ function and marks
the stack.

The second implementation could have orthagonal syntax:
extern "C": non-throwing C function
extern "C callback": throw-through C function
extern "Pascal": non-throwing Pascal convention function
extern "Pascal callback": throw-through Pascal function
extern "WinAPI": non-throwing Windows API function
extern "WinAPI callback": Windows API callback function

This implementation, while not portable, is certainly conforming.
--
Bradd W. Szonye (bradds@ix.netcom.com), Doubleplus Corporation

"To chill or to pop a cap in my dome, whoomp, there it is."
   -- Hamlet, Prince of Denmark
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: David Brownell <brownell@ix.netcom.com>
Date: 1996/05/09
Raw View
Paul D. DeRocco wrote:
>
> Scott Meyers wrote:
>
> > Given that the syntax of function pointers is one of the best arguments
> > for typedefs and given that exception specs are checked during
> > initialization and assignment of function pointers, the explicit
> > restriction against the use of exception specs in typedefs seems almost
> > cruel.  Unless there is a better argument against allowing exception
> > specs in typedefs, I think the prohibition should be reconsidered.

I'd like to see that prohibition removed, too.  Also, I'd like to see the
interpretation of functions without exception specs be that they throw NO
exceptions, not "ANY" exception ... but that would be tough to fix now.

> True. However, as I explained in another thread, I think exception specs,
> other than to identify functions which can't throw anything, aren't
> particularly useful except as a kind of assertion during debugging. At least
> in the Borland compiler, they impose significant overhead, even when no
> exception is actually thrown. There's no easy answer, I'm afraid. I've
> simply given up on them.

I did notice Borland has an expensive implementation.  That's not needful;
SunSoft's C++ compiler is better in terms of space overhead (and has no time
overhead in "normal" cases, it's the range-table stuff).

It'll be interesting to see how Microsoft implements exception specs, when
they do.  I'd hope it would be a fast and inexpensive implementation, rather
than a subtle way to penalize applications.

- Dave
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  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: "Bradd W. Szonye" <bradds@ix.netcom.com>
Date: 1996/05/11
Raw View
On Thursday, May 09, 1996, David Brownell wrote...
> It'll be interesting to see how Microsoft implements exception specs, when
> they do.  I'd hope it would be a fast and inexpensive implementation, rather
> than a subtle way to penalize applications.

I'm intimately familiar with Microsoft's EH implementation, and it doesn't
seem like adding exception specs would be a major source of (additional)
overhead. Their implementation is rather slick; the Win32 OS itself
supports stack unwinding semantics very well. It uses a stack-marking
mechanism as opposed to a range-table mechanism. This has its pros and
cons:

Cons:
* Stack-marking imposes a speed overhead on programs that do not throw
exceptions
* The C++ catch(...) syntax catches asynchronous OS exceptions as well as
C++ exceptions (possibly a pro, if that's what you want, but the semantics
of this are tricky)

Pros:
* Stack-marking performs well even in the presence of non-C++ programs
that do not set up frame pointers correctly
* Stack-marking is partly supported by the OS, lessening the code impact
on programs slightly
* The Microsoft C compiler has extensions also supporting this method of
EH

I personally prefer the range-table implementation. Before I started
following the net discussions, I tried to think of an implementation that
avoids Microsoft's run-time speed penalty. I came up independently with a
range-table method that imposes a speed penalty only in the presence of
non-C++ functions which do not preserve frame pointers correctly. The
trouble situation is this:

A C++ function calls a non-C++ function that does not preserve the stack
frame pointer. The non-C++ function calls a C++ callback function that
throws an exception. The unwinding mechanism reaches the non-C++ function,
which has basically trashed the stack by not setting up a frame properly.

There are two good solutions to this: either the first C++ function does
an explicit stack mark so the EH mechanism can skip the "bad" function, or
the EH mechanism has no choice but to terminate().
--
Bradd W. Szonye (bradds@ix.netcom.com), Doubleplus Corporation

"To chill or to pop a cap in my dome, whoomp, there it is."
   -- Hamlet, Prince of Denmark
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  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: Jack Reeves <76217.2354@CompuServe.COM>
Date: 1996/04/24
Raw View
Can anyone explain why the DWP explicity disallows exception
specifications on a typedef?
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: "Paul D. DeRocco" <pderocco@ix.netcom.com>
Date: 1996/04/26
Raw View
Jack Reeves wrote:
> Can anyone explain why the DWP explicity disallows exception
> specifications on a typedef?

Because it's not part of the type. And a good thing too, since exception
specs are far more likely to change in a library routine than the actual
arg and return types.

--

Ciao,
Paul D. DeRocco
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: "Paul D. DeRocco" <pderocco@ix.netcom.com>
Date: 1996/05/06
Raw View
Scott Meyers wrote:

> Given that the syntax of function pointers is one of the best arguments for
> typedefs and given that exception specs are checked during initialization
> and assignment of function pointers, the explicit restriction against the
> use of exception specs in typedefs seems almost cruel.  Unless there is a
> better argument against allowing exception specs in typedefs, I think the
> prohibition should be reconsidered.

True. However, as I explained in another thread, I think exception specs,
other than to identify functions which can't throw anything, aren't
particularly useful except as a kind of assertion during debugging. At least
in the Borland compiler, they impose significant overhead, even when no
exception is actually thrown. There's no easy answer, I'm afraid. I've simply
given up on them.

--

Ciao,
Paul D. DeRocco
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  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: smeyers@teleport.com (Scott Meyers)
Date: 1996/04/30
Raw View
Paul D. DeRocco <pderocco@ix.netcom.com> wrote:
| Jack Reeves wrote:
| > Can anyone explain why the DWP explicity disallows exception
| > specifications on a typedef?
|
| Because it's not part of the type. And a good thing too, since exception
| specs are far more likely to change in a library routine than the actual
| arg and return types.

Exception specs may not technically be part of a function's type, but note
that they are checked in many cases.  From DWP 15.4:


  2 If  any  declaration of a function has an exception-specification, all
    declarations, including the definition, of that function shall have an
    exception-specification  with  the same set of type-ids.  If a virtual
    function has an exception-specification, all  declarations,  including
    the  definition,  of any function that overrides that virtual function
    in any derived class shall have an exception-specification at least as
    restrictive as that in the base class.  [Example:
            struct B {
                virtual void f() throw (int, double);
                virtual void g();
            };

            struct D: B {
                void f();                    // ill-formed
                void g() throw (int);        // OK
            };
      --end  example]  The  declaration  of  D::f is ill-formed because it
    allows all exceptions, whereas B::f allows only int and double.  Simi-
    larly,  any function or pointer to function assigned to, or initializ-
    ing, a pointer to function shall have  an  exception-specification  at
    least as restrictive as that of the pointer or function being assigned
    to or initialized.  [Example:
            void (*pf1)();    // no exception specification
            void (*pf2) throw(A);

            void f()
            {
                    pf1 = pf2;  // ok: pf1 is less restrictive
                    pf2 = pf1;  // error: pf2 is more restrictive
            }
     --end example]

  3 In such an assignment or initialization,  exception-specifications  on
    return types and parameter types shall match exactly.

  4 In  other  assignments  or  initializations,  exception-specifications
    shall match exactly.

Given that the syntax of function pointers is one of the best arguments for
typedefs and given that exception specs are checked during initialization
and assignment of function pointers, the explicit restriction against the
use of exception specs in typedefs seems almost cruel.  Unless there is a
better argument against allowing exception specs in typedefs, I think the
prohibition should be reconsidered.

Scott


[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  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: pabloh@hpwala.wal.hp.com (Pablo Halpern )
Date: 12 Oct 92 16:44:09 GMT
Raw View
In article <KANZE.92Oct7151332@slsvdnt.us-es.sel.de>, kanze@us-es.sel.de (James Kanze) writes:
|> This leads to my basic premise.
|>
|> Objects in a general library should not throw exceptions at all.  The
|> how and what of exception handling is *always* application specific,
|> and if a library like the iostream starts throwing exceptions, it
|> forces *all* applications that use it into its mould.
|>
|> The solution I prefer is call-back functions.  The default failure
|> action is always to terminate the program with an error message.
|> Since this occurs near the point of the error, the error message can
|> actually be quite meaningful.  In failsafe applications, the user
|> replaces the default error function with one of his own, which may
|> very well (probably will) throw an exception.  This will be an
|> exception designed by the user however, and not one imposed by the
|> library.  The choice of whether one or many exception classes, etc.,
|> and in what class hierarchy they are, can then be made in an
|> application specific manner by the user.

Sorry to waste bandwidth on such a short follow-up, but I just want to
re-enforce James' premise and his call-back solution.  Let the client of a
general library choose what type of action to take for an error in a
general-purpose library.  Using this mechanism for the default allocator
would allow a GUI-based application to automatically display an "Out of
Memory" message and terminate, for example, rather than raise an exception.

--

- Pablo

------------------------------------------------------------------------
Pablo Halpern             (617) 290-3542
HP Waltham                pabloh@hpwarq.wal.hp.com

I am self-employed, so my opinions *do* reflect those of my employer.
However, they may not reflect the opinions of my client.
------------------------------------------------------------------------




Author: kanze@us-es.sel.de (James Kanze)
Date: 7 Oct 92 15:13:32
Raw View
In article <1992Oct01.143355.27847@bnr.ca>, Matthew Holiday writes:

|> In article <1992Sep30.205656.10646@lucid.com>, jss@lucid.com (Jerry
|> Schwarz) writes:

|> |> At the moment X3J16 is considering the implications of exceptions
|> |> for stream classes.  Some think that streams should only
|> |> be able to throw a particular kind of exception (tentatively
|> |> named ios::failure). Others (including myself) feel that a stream
|> |> should be able to throw an arbitrary exception. Remember, users
|> |> can define their own kinds of streambufs, so X3J16 can't know in advance
|> |> what kinds of conditions they might want to report.
|> |>
|> |> My intuition (not based on experience I confess) is that in
|> |> general it is more important to get specific information about
|> |> the conditions that caused an exception than it it to know that
|> |> it in occured in some stream operation. That is, it is better to
|> |> know that there was an "out of memory" or "database corruption" problem
|> |> than it is to know that there was a problem while doing a stream
|> |> operation.  This leads me to the conclusion that the iostream library
|> |> should not use failure clauses.

|> One thing that bugs me about having "lots" of exception types that
|> could be thrown is the degree of coupling that will result between
|> parts of the program.

|> If an object can throw 18 exception types, then clients will need to
|> understand all those types, and their implications -- and perhaps
|> then, something of the internal workings of that object, in order to
|> understand how to respond to a particular exception.

|> It might be better to cause the functions in the objects interface
|> (the top level functions of the object) to throw only a limited set of
|> exceptions, perhaps only one, and handle all other exceptions
|> themselves (print an error message or log, take some constructive
|> action, etc.).  The client's response to failure of an object might be
|> to destroy and recreate the object, and reattempt the operation, or
|> call a "recover" function in that object, etc.

|> There is a measure of philosophical difference here.  If all parts of
|> the program are written by the same team, then the team can determine
|> the safe degree of coupling between objects and their clients.  On the
|> other hand, if a program is to be created from reusable (perhaps
|> third-party) software libraries, then coupling should be minimized --
|> otherwise, why use objects at all?

This leads to my basic premise.

Objects in a general library should not throw exceptions at all.  The
how and what of exception handling is *always* application specific,
and if a library like the iostream starts throwing exceptions, it
forces *all* applications that use it into its mould.

The solution I prefer is call-back functions.  The default failure
action is always to terminate the program with an error message.
Since this occurs near the point of the error, the error message can
actually be quite meaningful.  In failsafe applications, the user
replaces the default error function with one of his own, which may
very well (probably will) throw an exception.  This will be an
exception designed by the user however, and not one imposed by the
library.  The choice of whether one or many exception classes, etc.,
and in what class hierarchy they are, can then be made in an
application specific manner by the user.
--
James Kanze                     GABI Software, Sarl.
email: kanze@us-es.sel.de       8 rue du Faisan
                                67000 Strasbourg
                                France




Author: jbn@lulea.trab.se (Johan Bengtsson)
Date: 1 Oct 92 11:47:28 GMT
Raw View
jss@lucid.com (Jerry Schwarz) writes:
:
: The later included the only people (a medical software group at HP)
: with experience using exceptions in C++. I asked them whether "out
: of memory" had to always be included on their lists and they said
: they didn't treat "out of memory" as an exception.

I think the issue is broader than that.  It is likely (IMO) that certain
"system failure" exceptions will occur at unexpected times.  Examples
include "out of memory", "invalid pointer dereference", "divide by zero",
"OODB deadlock" etc.

For a program where it is unacceptable to terminate when this happens,
the ARM provides the opportunity to replace the predefined unexpected()
function with a custom one.  Suppose you wish to nevertheless
propagate these exceptions to a CATCH block, no matter what
the exception specifications say.  Is it OK for unexpected() to
simply rethrow the exception?

If this is _not_ allowed, then I'm afraid many functions (for example
in life-critical systems) _must_ specify every "system failure" like
exception, causing an avalanche of these throughout the entire program.
Commercial libraries will need these too (to enable the broadest usage).

: At the moment X3J16 is considering the implications of exceptions
: for stream classes.  Some think that streams should only
: be able to throw a particular kind of exception (tentatively
: named ios::failure). Others (including myself) feel that a stream
: should be able to throw an arbitrary exception. Remember, users
: can define their own kinds of streambufs, so X3J16 can't know in advance
: what kinds of conditions they might want to report.

But they may inherit their exception types from ios::failure, no?
Does bring more structure to stream error handling, IMHO.
Derived streambufs will be required to catch and translate non-ios
exceptions.

: [...]This leads me to the conclusion that the iostream library
: should not use failure clauses.

But this means every function using streams must catch all exceptions,
or refrain from using exception clauses, and the functions calling those
functions, and so on.  We end up with programs with either a lot
CATCH blocks, or very few failure clauses.

--
--------------------------------------------------------------------------
| Johan Bengtsson, Telia Research AB, Aurorum 6, S-951 75 Lulea, Sweden  |
| Johan.Bengtsson@lulea.trab.se; Voice:(+46)92075471; Fax:(+46)92075490  |
--------------------------------------------------------------------------




Author: vinoski@apollo.hp.com (Stephen Vinoski)
Date: Thu, 1 Oct 1992 14:36:14 GMT
Raw View
In article <1992Sep30.205656.10646@lucid.com> jss@lucid.com (Jerry Schwarz) writes:
>At the moment X3J16 is considering the implications of exceptions
>for stream classes.  Some think that streams should only
>be able to throw a particular kind of exception (tentatively
>named ios::failure). Others (including myself) feel that a stream
>should be able to throw an arbitrary exception. Remember, users
>can define their own kinds of streambufs, so X3J16 can't know in advance
>what kinds of conditions they might want to report.
>
>My intuition (not based on experience I confess) is that in
>general it is more important to get specific information about
>the conditions that caused an exception than it it to know that
>it in occured in some stream operation. That is, it is better to
>know that there was an "out of memory" or "database corruption" problem
>than it is to know that there was a problem while doing a stream
>operation.  This leads me to the conclusion that the iostream library
>should not use failure clauses.

It seems like a compromise -- making ios::failure into a base class
from which other more specific exception types can be derived -- would
be the best approach.  Application code that can do something useful
with specific exceptions can then arrange to catch each one
individually, while code that cares only that an exception was thrown
but does not wish to catch each different type can arrange to catch
only the base class.

-steve
--
Steve Vinoski  (508)436-5904   vinoski@apollo.hp.com
Distributed Object Computing Program
Hewlett-Packard, Chelmsford, MA 01824       These are my opinions.




Author: holiday@bnr.ca (Matthew Holiday)
Date: Thu, 01 Oct 1992 14:33:55 GMT
Raw View
In article <1992Sep30.205656.10646@lucid.com>, jss@lucid.com (Jerry Schwarz) writes:
|>
|> At the moment X3J16 is considering the implications of exceptions
|> for stream classes.  Some think that streams should only
|> be able to throw a particular kind of exception (tentatively
|> named ios::failure). Others (including myself) feel that a stream
|> should be able to throw an arbitrary exception. Remember, users
|> can define their own kinds of streambufs, so X3J16 can't know in advance
|> what kinds of conditions they might want to report.
|>
|> My intuition (not based on experience I confess) is that in
|> general it is more important to get specific information about
|> the conditions that caused an exception than it it to know that
|> it in occured in some stream operation. That is, it is better to
|> know that there was an "out of memory" or "database corruption" problem
|> than it is to know that there was a problem while doing a stream
|> operation.  This leads me to the conclusion that the iostream library
|> should not use failure clauses.

One thing that bugs me about having "lots" of exception types that could be
thrown is the degree of coupling that will result between parts of the program.

If an object can throw 18 exception types, then clients will need to understand
all those types, and their implications -- and perhaps then, something of the
internal workings of that object, in order to understand how to respond to a
particular exception.

It might be better to cause the functions in the objects interface (the top
level functions of the object) to throw only a limited set of exceptions, perhaps
only one, and handle all other exceptions themselves (print an error message or
log, take some constructive action, etc.).  The client's response to failure
of an object might be to destroy and recreate the object, and reattempt the
operation, or call a "recover" function in that object, etc.

There is a measure of philosophical difference here.  If all parts of the program
are written by the same team, then the team can determine the safe degree of
coupling between objects and their clients.  On the other hand, if a program is
to be created from reusable (perhaps third-party) software libraries, then coupling
should be minimized -- otherwise, why use objects at all?

--
Matt Holiday                                         #include <std/disclaimer>
holiday@bnr.ca
BNR Richardson, TX




Author: holiday@bnr.ca (Matthew Holiday)
Date: Thu, 01 Oct 1992 14:23:02 GMT
Raw View
In article <1992Sep29.201603.19289@cs.brown.edu>, sdm@cs.brown.edu (Scott Meyers) writes:
|> The more I think about it, the more I'm convinced that the uses for
|> exception specifications are astonishingly restricted.  As I see it, you
|> can't reliably put an exception specification on a function f if any of the
|> following are true:
|>
|>     1.  A call to f might directly or indirectly lead to a call to g where
|>         g lacks an exception specification.  A special case of this is
|>         where g is a function written in another language, e.g., anything
|>         in the standard C library.  Recall that functions without exception
|>         specifications may throw anything.
|>
|>     2.  f is a template function.  In this case, f must be instantiated
|>         using some unknown type T, and it is highly likey that f will
|>         invoke some T-specific function (e.g. member function of class T).
|>         However, there is no way to know what the exception specifications
|>         are on the T-specific functions.
|>
|>     3.  f is a virtual function, because f may be redefined in a subclass
|>         by some function f' that contains an incompatible exception
|>         specification.  Recall that an exception specification is not part
|>         of a function's type.
|>

I don't think (1) and (2) are real problems; the funtion f should catch all
exceptions except the ones it lists in its exception specification.

(3) is a real problem; either the rules on exception specifications as part
of the function signature need to be changed (a la Modula-3), or they won't
be very safe.  I'd rather see them done as in Modula-3 (but still allow the
default -- no specification -- to allow all exceptions to be thrown).

--
Matt Holiday                                      #include <std/disclaimer>
holiday@bnr.ca
BNR Richardson, TX




Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: Thu, 1 Oct 1992 15:10:52 GMT
Raw View
In article <5035@holden.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
>fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>: sakkinen@jyu.fi (Markku Sakkinen) writes:
>:
>: >I am afraid that distinguishing exceptions only by the type of the
>: >'throw' expression is mostly overkill instead of improvement.
>: > ...
>: Is this so difficult?
>: ...
>:  class MyException : Exception {};

Here is only 1 exception, not 20.

>:
>:  struct MyIntException : Exception {
>:      int value;
>:      MyIntException(int value_) : value(value_) {}
>:  };

Likewise.

>
>Note that Markku said distinguishing exceptions only by the type.
>                                                ^^^^
>The alternative to class MyIntException with 20 distinct instances
>is 20 subclasses to MyIntException (which IMHO _is_ overkill).
> ...

Yes, that's what I had in mind.  Or actually that those exceptions
would not be related to each other at all, thus no inheritance relationship.
In Ada, you would write:
  excp1, excp2, excp2, ..., excp20: exception;
In C++, you would write:
  struct excp1 {};
  struct excp2 {};
   ...
  struct excp20 {};
and document somewhere that these classes are meant to function
as "exception identifiers".
(Admitted, if the exceptions are somehow related, one can declare
them as the constants of a new enumeration type, as suggested
in Stroustrup's book.  The situation is then almost as in Ada.)

Of course, one defect in Ada's exception mechanism is that no additional
information can be passed together with the exception itself,
and that has been corrected in C++.

Inheritance between exceptions may be treacherous, since 'throw'
does not work "virtually".  E.g., with Fergus's example:

MyIntException mye (77);
Exception *ep = mye;
throw *ep;

Only Exception is thrown!

----------------------------------------------------------------------
Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------




Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: Fri, 2 Oct 1992 12:52:44 GMT
Raw View
In article <9227620.2176@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>sakkinen@jyu.fi (Markku Sakkinen) writes:
>> ...
>I guess it is true that programmers just don't like verbosity.
>OK, here is the succinctness-at-all-costs solution:
>
> #define e(x) class x : Exception {} ;
> e(e1)  e(e2)  e(e3)  e(e4)  e(e5)  e(e6)  e(e7)  e(e8)  e(e9)  e(e10)
> e(e11) e(e12) e(e13) e(e14) e(e15) e(e16) e(e17) e(e18) e(e19) e(e20)
>
>Is that short enough, or should I remove all the unnecessary whitespace? ;-)

Not being a C or C++ fan, I don't like succinctness at all costs.
In this case, I like succinctness without cost a la Ada.

> ...
>Actually, in C++ I would write
>   struct excp1 : Exception {};
>  ^^^^^^^^^^^

What would that buy you?  See below.

>>Inheritance between exceptions may be treacherous, since 'throw'
>>does not work "virtually".  E.g., with Fergus's example:
>> ...
> ...
>You can make throw behave virtually by throwing a pointer to the exception
>instead of the exception itself:

(Or a reference.)  Yes, I must admit that this problem is not very severe.

> throw new SomeException(...)
>although you need to take care to ensure that the storage is correctly
>deallocated by the handler, to avoid memory leaks.

The example is not the best possible one, since here the exact type
of the referenced object is in fact statically known.
The handler has no way to know when it has to delete the exception object,
short of there being a quasi-Boolean field 'delete_me' in Exception.
Thus:

struct Exception { int delete_me; };
throw new Exception (1);
Exception ex (0);
throw ex;

And now to a perhaps better reason why using inheritance with exceptions
(as suggested by you above) may be treacherous.
Whenever there exists a handler for _any_ ancestor (base class) of the
class of the exception object, that handler will be happily executed.
Thus, if an instance of a class derived from Exception is thrown,
there is no guarantee that any more specific handler than that
of Exception will be invoked.
This is essentially different from virtual functions, with which it is
guaranteed that dynamic binding will find the function appropriate
for the true class of the 'this' object.

----------------------------------------------------------------------
Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------




Author: niklas@appli.se (Niklas Hallqvist)
Date: 2 Oct 92 13:33:43 GMT
Raw View
sakkinen@jyu.fi (Markku Sakkinen) writes:

:Inheritance between exceptions may be treacherous, since 'throw'
:does not work "virtually".  E.g., with Fergus's example:

:MyIntException mye (77);
:Exception *ep = mye;
:throw *ep;

:Only Exception is thrown!

Of course, that's no different from an ordinary call.  If you want
virtualness, you have to use pointers or references, as usual.

class Failure {
public:
  virtual int retry() = 0;
};

template < class T >
class MemFailure {
  int (*a_retry)(const T&);
  T a_context;
protected:
  virtual void get_more_memory () { free (some_extra_storage); }
public:
  MemFailure (int (*the_retry)(const T&), T the_context)
    : a_retry (the_retry), a_context (the_context) {}
  int retry() { get_more_memory (); return a_retry(a_context); }
};

void foo()
{
  try
    {
      bar ();
    }
  catch (Failure f) // Oops!
    {
      if (f.retry ()) // Pure method call :-(
 throw;
    }
  try
    {
      bar ();
    }
  catch (Failure& f) // OK!
    {
      if (f.retry ())
 throw;
    }
}
int my_retry (int)
{
  return 1;
}

void bar ()
{
  static MemFailure<int> mf(my_retry, 0);
  Failure& rf = mf;
  Failure* pf = &mf;
  switch (rand () & 1)
    {
    case 0:
      throw rf; // OK!
      break;
    case 1:
      throw *pf; // Oops, slicing :-(
      break;
    }
}

Niklas
--
Niklas Hallqvist Phone: +46-(0)31-40 75 00
Applitron Datasystem Fax:   +46-(0)31-83 39 50
Molndalsvagen 95 Email: niklas@appli.se
S-412 63  GOTEBORG, Sweden     mcsun!seunet!appli!niklas




Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Fri, 2 Oct 1992 19:22:09 GMT
Raw View
[Sorry if this reaches anyone twice.
 The previous version had an unfortunate typo.]

sakkinen@jyu.fi (Markku Sakkinen) writes:

>jbn@lulea.trab.se (Johan Bengtsson) writes:
>>fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>>: sakkinen@jyu.fi (Markku Sakkinen) writes:
>>:
>>: >I am afraid that distinguishing exceptions only by the type of the
>>: >'throw' expression is mostly overkill instead of improvement.
>>: > ...
>>: Is this so difficult?
>>: ...
>>:  class MyException : Exception {};
>
>Here is only 1 exception, not 20.

I guess it is true that programmers just don't like verbosity.
OK, here is the succinctness-at-all-costs solution:

 #define e(x) class x : Exception {} ;
 e(e1)  e(e2)  e(e3)  e(e4)  e(e5)  e(e6)  e(e7)  e(e8)  e(e9)  e(e10)
 e(e11) e(e12) e(e13) e(e14) e(e15) e(e16) e(e17) e(e18) e(e19) e(e20)

Is that short enough, or should I remove all the unnecessary whitespace? ;-)

>In Ada, you would write:
>  excp1, excp2, excp2, ..., excp20: exception;
>In C++, you would write:
>  struct excp1 {};
>  struct excp2 {};
>   ...
>  struct excp20 {};
>and document somewhere that these classes are meant to function
>as "exception identifiers".

Actually, in C++ I would write
   struct excp1 : Exception {};
  ^^^^^^^^^^^

>Inheritance between exceptions may be treacherous, since 'throw'
>does not work "virtually".  E.g., with Fergus's example:
>
>MyIntException mye (77);
>Exception *ep = mye;
This should be = &mye;
>throw *ep;
>
>Only Exception is thrown!

I think that this is unlikely to be a problem, since almost invariably
you know exactly what type of exception you wish to throw at the time
you are throwing it:

 if (some error condition) {
     throw SomeException(...);
 }

It's a bit like constructors not being virtual: this is not a problem, since
when you are constructing an object, you know what type should be.

You can make throw behave virtually by throwing a pointer to the exception
instead of the exception itself:
 throw new SomeException(...)
although you need to take care to ensure that the storage is correctly
deallocated by the handler, to avoid memory leaks.

Note that "catch" *does* behave virtually.

 class Foo : Exception {
     virtual void foo() { cout << "Foo::foo()" << endl; }
 };
 class Bar : Foo {
     virtual void foo() { cout << "Bar::foo()" << endl; }

 main() {
     try { throw Bar; }   // [ Fixed bug here :-) ]
     catch (Foo& f) { f.foo(); }
 }

This will print Bar::foo(), not Foo::foo().

--
Fergus Henderson             fjh@munta.cs.mu.OZ.AU
This .signature virus is a self-referential statement that is true - but
you will only be able to consistently believe it if you copy it to your own
.signature file!





Author: ark@alice.att.com (Andrew Koenig)
Date: 2 Oct 92 19:02:13 GMT
Raw View
In article <1992Oct2.125244.14614@jyu.fi> sakkinen@jyu.fi (Markku Sakkinen) writes:

> The handler has no way to know when it has to delete the exception object,
> short of there being a quasi-Boolean field 'delete_me' in Exception.
> Thus:

> struct Exception { int delete_me; };
> throw new Exception (1);
> Exception ex (0);
> throw ex;

These problems do not occur if you throw objects (rather than pointers)
and catch references.  In that case, the implementation takes care of
deleting things at the proper time.
--
    --Andrew Koenig
      ark@europa.att.com




Author: pena@niksula.hut.fi (Olli-Matti Penttinen)
Date: 3 Oct 92 01:54:09
Raw View
Matt> If an object can throw 18 exception types, then clients will need
Matt> to understand all those types, and their implications -- and
Matt> perhaps then, something of the internal workings of that object,
Matt> in order to understand how to respond to a particular exception.

By carefully deriving exception types, the clients needn't worry about
details they really don't understand or care about.

Matt> It might be better to cause the functions in the objects
Matt> interface (the top level functions of the object) to throw only a
Matt> limited set of exceptions, perhaps only one, and handle all other
Matt> exceptions themselves (print an error message or log, take some
Matt> constructive action, etc.).

Rather, let them throw any number of exceptions, but make sure the
exception types have a limited number of distinct base classes. The
ability to derive exception types is the very feature that makes C++
exceptions superior to -- say, Ada's. Client code can catch them with
the accuracy it needs to instead of what the library says.


==pena
--
Olli-Matti Penttinen <pena@niksula.cs.hut.fi>
Lehdesniityntie 3 F 91
00340 HELSINKI                     "When in doubt, use brute force."
tel. + 358 0 1399 0110                -- Ken Thompson




Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Sat, 3 Oct 1992 04:24:22 GMT
Raw View
sakkinen@jyu.fi (Markku Sakkinen) writes:

>fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>> ...
>>Actually, in C++ I would write
>>   struct excp1 : Exception {};
>>  ^^^^^^^^^^^
>
>What would that buy you?  See below.
>
>>>Inheritance between exceptions may be treacherous, since 'throw'
>>>does not work "virtually".  E.g., with Fergus's example:
>>> ...
>> ...
>>You can make throw behave virtually by throwing a pointer to the exception
>>instead of the exception itself:
>
>(Or a reference.)  Yes, I must admit that this problem is not very severe.

You can't throw a reference.

 Exception value;
 Exception& reference = value;
 throw reference;

This will throw the value refered to by the reference, not the reference
itself.  [Well, this is what I infer from reading the RM]

>> throw new SomeException(...)
>>although you need to take care to ensure that the storage is correctly
>>deallocated by the handler, to avoid memory leaks.
>
>The example is not the best possible one, since here the exact type
>of the referenced object is in fact statically known.
>The handler has no way to know when it has to delete the exception object,
>short of there being a quasi-Boolean field 'delete_me' in Exception.
>Thus:
>
>struct Exception { int delete_me; };
>throw new Exception (1);
 This throws an (Exception *)
>Exception ex (0);
>throw ex;
 This just throws an Exception.
 So you can distinguish between them by

 try ...
 catch(Exception *e_ptr) {
  ...
  delete e_ptr;
 }
 catch(Exception& e) {
  ...
 }

>And now to a perhaps better reason why using inheritance with exceptions
>(as suggested by you above) may be treacherous.
>Whenever there exists a handler for _any_ ancestor (base class) of the
>class of the exception object, that handler will be happily executed.
>Thus, if an instance of a class derived from Exception is thrown,
>there is no guarantee that any more specific handler than that
>of Exception will be invoked.
>This is essentially different from virtual functions, with which it is
>guaranteed that dynamic binding will find the function appropriate
>for the true class of the 'this' object.

Actually, this is just a reason to always catch exceptions by reference.
Then the handler can call virtual functions in the exception object.

Raising an exception is then equivalent to passing an object to a function
that takes a reference parameter. Of course, in both cases you can't
*guarantee* that the exception handler / receiving function will actually
call any virtual functions, but you know that if it does then the behaviour
will be appropriately polymorphic.

--
Fergus Henderson             fjh@munta.cs.mu.OZ.AU
This .signature virus is a self-referential statement that is true - but
you will only be able to consistently believe it if you copy it to your own
.signature file!




Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: Mon, 5 Oct 1992 07:35:09 GMT
Raw View
In article <23785@alice.att.com> ark@alice.UUCP () writes:
>In article <1992Oct2.125244.14614@jyu.fi> sakkinen@jyu.fi (Markku Sakkinen) writes:
>
>> The handler has no way to know when it has to delete the exception object,
>> short of there being a quasi-Boolean field 'delete_me' in Exception.
>> ...
>
>These problems do not occur if you throw objects (rather than pointers)
>and catch references.  In that case, the implementation takes care of
>deleting things at the proper time.

That is impossible!  If the exception argument has been allocated by 'new',
the implementation cannot guess whether it should be deleted or not.
It may well be an object used by an outer block or function,
and which has not as its _only_ purpose to convey exception information.

----------------------------------------------------------------------
Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------




Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: Mon, 5 Oct 1992 12:58:19 GMT
Raw View
In article <1992Oct5.073509.1188@jyu.fi> sakkinen@jyu.fi (myself) writes:
>In article <23785@alice.att.com> ark@alice.UUCP () writes:
>>In article <1992Oct2.125244.14614@jyu.fi> sakkinen@jyu.fi (Markku Sakkinen) writes:
>>These problems do not occur if you throw objects (rather than pointers)
>>and catch references.  In that case, the implementation takes care of
>>deleting things at the proper time.
>
>That is impossible!  If the exception argument has been allocated by 'new',
>the implementation cannot guess whether it should be deleted or not.
>It may well be an object used by an outer block or function,
>and which has not as its _only_ purpose to convey exception information.

Well, now I see that you must have had a tacit assumption:
that to 'throw objects' means to throw variables (objects)
that are _not_ allocated dynamically (from the heap).
Then you are correct, of course.  Proper deallocation is in
general a very tough problem for C++ programmers only with dynamic objects.

----------------------------------------------------------------------
Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------




Author: sdm@cs.brown.edu (Scott Meyers)
Date: Tue, 29 Sep 1992 20:16:03 GMT
Raw View
The more I think about it, the more I'm convinced that the uses for
exception specifications are astonishingly restricted.  As I see it, you
can't reliably put an exception specification on a function f if any of the
following are true:

    1.  A call to f might directly or indirectly lead to a call to g where
        g lacks an exception specification.  A special case of this is
        where g is a function written in another language, e.g., anything
        in the standard C library.  Recall that functions without exception
        specifications may throw anything.

    2.  f is a template function.  In this case, f must be instantiated
        using some unknown type T, and it is highly likey that f will
        invoke some T-specific function (e.g. member function of class T).
        However, there is no way to know what the exception specifications
        are on the T-specific functions.

    3.  f is a virtual function, because f may be redefined in a subclass
        by some function f' that contains an incompatible exception
        specification.  Recall that an exception specification is not part
        of a function's type.

The ARM refers to an exception specification as a "promise" (ARM 15.6.2c),
but the above points make clear that this promise can't be given very
often, or at least can't be relied upon very often.  Similarly, the
discussion of why exception specifications are so lax is discussed in ARM
15.5c.  The ARM also implies that special-purpose static analyzers would be
required to do much of anything with exception specifications, especially
in the presence of separate compilation.  (For example, if f contains an
exception specification and calls virtual function g which also contains an
exception specification, no warning can be issued if g is redefined by an
incompatible g' in a derived class that is contained in a separate
compilation unit.)

All of which leads me to believe that exception specifications are not
really designed for the "general use" suggested in the last paragraph of
commentary in ARM 15.5c.  That being the case, I'm tempted to suggest they
be removed from the language.  For the specialized applications that can
make use of them, an Anna-like annotation language seems more appropriate
than actual language support within C++.

Comments?

Scott


-------------------------------------------------------------------------------
What do you say to a convicted felon in Providence?  "Hello, Mr. Mayor."




Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Wed, 30 Sep 1992 05:37:41 GMT
Raw View
sdm@cs.brown.edu (Scott Meyers) writes:

>The more I think about it, the more I'm convinced that the uses for
>exception specifications are astonishingly restricted.  As I see it, you
>can't reliably put an exception specification on a function f if any of the
>following are true:
>
>    1.  A call to f might directly or indirectly lead to a call to g where
>        g lacks an exception specification.  A special case of this is
>        where g is a function written in another language, e.g., anything
>        in the standard C library.  Recall that functions without exception
>        specifications may throw anything.

What's wrong with
    extern "C" void g(void) throw ();
?

The grammar given in C++PL 2nd Ed. includes the non-terminal
"exception-specification", but this non-terminal is not refered to in the
rest of the grammar. Presumeably the production

    declarator : declarator ( argument-declaration-list ) cv-qualifier-list<opt>

should include exception-specification<opt> either before or after the
cv-qualifier-list<opt>, which would then mean that the above extern "C"
declaration is legal.

[By the way, which is it - before or after? Which of the following are legal:
 class X {
  void g() throw() const;  // before
  void f() const throw();  // after
 };
Or are both legal?]


>    2.  f is a template function.  In this case, f must be instantiated
>        using some unknown type T, and it is highly likey that f will
>        invoke some T-specific function (e.g. member function of class T).
>        However, there is no way to know what the exception specifications
>        are on the T-specific functions.
>
>    3.  f is a virtual function, because f may be redefined in a subclass
>        by some function f' that contains an incompatible exception
>        specification.  Recall that an exception specification is not part
>        of a function's type.
>
>The ARM refers to an exception specification as a "promise" (ARM 15.6.2c),
>but the above points make clear that this promise can't be given very
>often, or at least can't be relied upon very often.  Similarly, the
>discussion of why exception specifications are so lax is discussed in ARM
>15.5c.  The ARM also implies that special-purpose static analyzers would be
>required to do much of anything with exception specifications, especially
>in the presence of separate compilation.  (For example, if f contains an
>exception specification and calls virtual function g which also contains an
>exception specification, no warning can be issued if g is redefined by an
>incompatible g' in a derived class that is contained in a separate
>compilation unit.)

In C++PL 2nd Ed, BS says "An implementation may not reject an expression
simply because it _may_ throw an exception not specified in an exception-
specification of the function containing the expression; the handling
of violations of an exception-specification is done at run-time."

However this does NOT mean that the implementation may not give WARNINGS
in such cases. In particular I would not be satisfied with a compiler that
did not give me a warning any time that I

 - call a function whose exception-specification is not a subset of
          the calling function's exception-specification;

 - redefine a virtual function with one whose exception-specification is
   not a subset of the exception-specification of the base class's
   function;

 - instantiate a template in such a manner as to cause either of the
   above.

----------------------------------------------------------------------

If an exception is raised in a destructor while unwinding the stack
from a previous exception, then the function terminate() is called.

This means that in general, destructors should NEVER raise any exceptions.
I want to be able to put that exception specification on all my destructors.

 ~X::X() throw() {...}

Furthermore, I want the compiler to check that at compile time, not at run-time.

--
Fergus Henderson             fjh@munta.cs.mu.OZ.AU
This .signature virus is a self-referential statement that is true - but
you will only be able to consistently believe it if you copy it to your own
.signature file!




Author: chased@rbbb.Eng.Sun.COM (David Chase)
Date: 30 Sep 92 02:21:49 GMT
Raw View
In article <1992Sep29.201603.19289@cs.brown.edu> sdm@cs.brown.edu (Scott Meyers) writes:
>The more I think about it, the more I'm convinced that the uses for
>exception specifications are astonishingly restricted.  ...
...
>All of which leads me to believe that exception specifications are not
>really designed for the "general use" suggested in the last paragraph of
>commentary in ARM 15.5c.  That being the case, I'm tempted to suggest they
>be removed from the language.

I'd suggest studying the exception specifications in Modula-3 (latest
version).  Those are based on years of experience with Cedar and
Modula-2+, as well as a study of the effects of changing the exception
specification rules a large body of software already written in
Modula-2+.  Modula-3 has "object methods" that are more or less
equivalent to C++ "virtual member functions", so the rebinding problem
does not go away.

As I recall, the M-3 rules are:

   no specification is equivalent to "no exceptions raised".

   the set of exceptions raised is part of a function type.

   assignment to function variables and/or binding to object methods
   must be type correct (i.e., in V := F, the set of exceptions raised
   by F must be a subset of the exceptions raised by V -- this is
   checked statically).

   if a function F calls a function G, static checking may provide
   a warning if G can raise an exception not in the list of exceptions
   raised by F.  If this happens at run-time, the exception is
   converted to a "failure exception" that can only be caught by
        TRY ... EXCEPT ... ELSE <catch it here> END
   though finalization clauses will be executed.

I do not know how this corresponds to the exception specifications for
C++; I only know that these are sane and that they work.  The use of
objects types as exception types (as in C++) appears to be compatible
with this, and an improvement (at least in the case of single
inheritance).

David Chase
Sun




Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: Wed, 30 Sep 1992 16:16:41 GMT
Raw View
In article <lci3ptINN3m3@exodus.Eng.Sun.COM> chased@rbbb.Eng.Sun.COM (David Chase) writes:
> ...
>I'd suggest studying the exception specifications in Modula-3 (latest
>version).  Those are based on years of experience with Cedar and
> ...
>I do not know how this corresponds to the exception specifications for
>C++; I only know that these are sane and that they work.  The use of
>objects types as exception types (as in C++) appears to be compatible
>with this, and an improvement (at least in the case of single
>inheritance).

I am afraid that distinguishing exceptions only by the type of the
'throw' expression is mostly overkill instead of improvement.
Suppose for instance that you have 20 distinct exceptions,
which only need to pass an integer's worth of information
to their handlers (or perhaps no data at all).
You have to declare a class for each of them.

John Skaller has remarked that the exception handling principle
requires run-time type information in the implementation,
a thing against which Bjarne Stroustrup has so steadfastly fought
all these years.  (Of course this does not necessarily mean that
the information would be available to the lowly programmers
to misuse.)

Obviously, inheritance between exceptions can be nice
in some situations.  That is one advantage of the C++ scheme.

----------------------------------------------------------------------
Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------




Author: pete@genghis.borland.com (Pete Becker)
Date: Wed, 30 Sep 1992 17:11:43 GMT
Raw View
In article <1992Sep29.201603.19289@cs.brown.edu> sdm@cs.brown.edu (Scott Meyers) writes:
>The more I think about it, the more I'm convinced that the uses for
>exception specifications are astonishingly restricted.  As I see it, you
>can't reliably put an exception specification on a function f if any of the
>following are true:
>
>    1.  A call to f might directly or indirectly lead to a call to g where
>        g lacks an exception specification.  A special case of this is
>        where g is a function written in another language, e.g., anything
>        in the standard C library.  Recall that functions without exception
>        specifications may throw anything.
>
>    2.  f is a template function.  In this case, f must be instantiated
>        using some unknown type T, and it is highly likey that f will
>        invoke some T-specific function (e.g. member function of class T).
>        However, there is no way to know what the exception specifications
>        are on the T-specific functions.
>
>    3.  f is a virtual function, because f may be redefined in a subclass
>        by some function f' that contains an incompatible exception
>        specification.  Recall that an exception specification is not part
>        of a function's type.
>
>The ARM refers to an exception specification as a "promise" (ARM 15.6.2c),
>but the above points make clear that this promise can't be given very
>often, or at least can't be relied upon very often.  Similarly, the
>discussion of why exception specifications are so lax is discussed in ARM
>15.5c.  The ARM also implies that special-purpose static analyzers would be
>required to do much of anything with exception specifications, especially
>in the presence of separate compilation.  (For example, if f contains an
>exception specification and calls virtual function g which also contains an
>exception specification, no warning can be issued if g is redefined by an
>incompatible g' in a derived class that is contained in a separate
>compilation unit.)
>
>All of which leads me to believe that exception specifications are not
>really designed for the "general use" suggested in the last paragraph of
>commentary in ARM 15.5c.  That being the case, I'm tempted to suggest they
>be removed from the language.  For the specialized applications that can
>make use of them, an Anna-like annotation language seems more appropriate
>than actual language support within C++.
>
>Comments?
>
>Scott
>

 I agree that these situations are conceptually difficult.  Take a look
at section 15.6.2, though: the unexpected() function is called when a function
with an exception-specification throws an exception that is not listed in the
exception-specification.  Presumably, the runtime support for exceptions is
supposed to handle checking that the exception thrown is one that does not
violate the exception-specification.  I don't have any experience with
exceptions, so I'm not in a position to comment on the usefulness of this
construct.




Author: jss@lucid.com (Jerry Schwarz)
Date: Wed, 30 Sep 92 20:56:56 GMT
Raw View
First a little history.

When exceptions were accepted by
X3J16 there were some of us (including myself) who though (like Scott)
that exception specifications were useless.  However, there were
also many who felt they were essential to good programming.
The later included the only people (a medical software group at HP)
with experience using exceptions in C++. I asked them whether "out
of memory" had to always be included on their lists and they said
they didn't treat "out of memory" as an exception.

The people who liked failure clauses would have prefered that
the default condition be the same as an empty failure list.
That is if you don't say that a function can throw an exception
then it can't.  The result was a compromise, failure clasues are allowed,
but the default is to allow functions without them to throw any
exception.

-----
Now my opinion.

My impression is that if you design your system from scratch
(including all libraries) with failure clauses on all function
declarations and your compiler checks them for consistency
you gain some useful compile time type checking.  But in
other circumstances there is little gained.

At the moment X3J16 is considering the implications of exceptions
for stream classes.  Some think that streams should only
be able to throw a particular kind of exception (tentatively
named ios::failure). Others (including myself) feel that a stream
should be able to throw an arbitrary exception. Remember, users
can define their own kinds of streambufs, so X3J16 can't know in advance
what kinds of conditions they might want to report.

My intuition (not based on experience I confess) is that in
general it is more important to get specific information about
the conditions that caused an exception than it it to know that
it in occured in some stream operation. That is, it is better to
know that there was an "out of memory" or "database corruption" problem
than it is to know that there was a problem while doing a stream
operation.  This leads me to the conclusion that the iostream library
should not use failure clauses.

  -- Jerry Schwarz

Please. No followups about how throwing exceptions by streams
will break existing programs.  We're aware of that.  By default
streams will throw (almost) no exceptions and will catch any thrown
by a streambuf.











Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Thu, 1 Oct 1992 05:52:16 GMT
Raw View
sakkinen@jyu.fi (Markku Sakkinen) writes:

>I am afraid that distinguishing exceptions only by the type of the
>'throw' expression is mostly overkill instead of improvement.
>Suppose for instance that you have 20 distinct exceptions,
>which only need to pass an integer's worth of information
>to their handlers (or perhaps no data at all).
>You have to declare a class for each of them.

Is this so difficult?

 class MyException : Exception {};

 struct MyIntException : Exception {
     int value;
     MyIntException(int value_) : value(value_) {}
 };

The amount of typing required does not seem to be excessive, IMHO.

>John Skaller has remarked that the exception handling principle
>requires run-time type information in the implementation,
>a thing against which Bjarne Stroustrup has so steadfastly fought
>all these years.  (Of course this does not necessarily mean that
>the information would be available to the lowly programmers
>to misuse.)

The run-time type information need only be supplied for objects which are
actually thrown, not for all objects.

------------------------------------------------------------------------

One possible implementation is as follows: for each class that may be thrown,
store the name of the class, together with pointers to all the classes it
is derived from (if any):

 struct __Type {
  const char *type_name;
  int num_base_classes;   // will be >1 for multiple inheritance
  __Type *base_classes[1];  // we're using the "struct hack" for
       // efficiency :-)
 };

So everytime we see a throw(x) statement, where x is an object of class X,
we generate the type info for X and all it's base classes, and store these in
global constants __type_info_for_class_X etc.

When an exception is thrown, we need to store both the object thrown and also
it's type info:

 void* __exception_object; // pointer to the object thrown
 __Type* __exception_type; // run-time type info

The code generated for the throw statement itself looks like:
 __exception_object = new X(x);
 __exception_type   = &__type_info_for_class_X;
 unwind_stack();

Then the code generated for a statement like
 catch(Matherr mm) { ... }
will look like
 if (__matches_type("Matherr", __exception_type)) {
     Matherr& mm = *(Matherr *)__exception_object;
     ...
 }
where __matches_type just does string comparison on the type names:

 int __matches_type(const char *type_name, Type *type) {

     // does it match the class name?
     if (strcmp(type_name, type->type_name) == 0)
  return TRUE;

     // does it match with any of the parent classes?
     for (int i = 0; i < type->num_base_classes)
  if (__matches_type(type_name, type->base_classes[i])
      return TRUE;

     // otherwise, no match found
     return FALSE;
 }

Have I confused you enough already?
Anyway, what I was trying to point out was that it really isn't that
complicated: exception handling only requires run-time type info for objects
that are actually throw, and run-time type identification can just involve
simple string comparisons.

--
Fergus Henderson             fjh@munta.cs.mu.OZ.AU
This .signature virus is a self-referential statement that is true - but
you will only be able to consistently believe it if you copy it to your own
.signature file!




Author: jbn@lulea.trab.se (Johan Bengtsson)
Date: 1 Oct 92 12:05:45 GMT
Raw View
fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
: sakkinen@jyu.fi (Markku Sakkinen) writes:
:
: >I am afraid that distinguishing exceptions only by the type of the
: >'throw' expression is mostly overkill instead of improvement.
: >Suppose for instance that you have 20 distinct exceptions,
: >which only need to pass an integer's worth of information
: >to their handlers (or perhaps no data at all).
: >You have to declare a class for each of them.
:
: Is this so difficult?
:
:  class MyException : Exception {};
:
:  struct MyIntException : Exception {
:      int value;
:      MyIntException(int value_) : value(value_) {}
:  };

Note that Markku said distinguishing exceptions only by the type.
                                                ^^^^
The alternative to class MyIntException with 20 distinct instances
is 20 subclasses to MyIntException (which IMHO _is_ overkill).

I do not advocate a single exception class, it just seems to me that
the exception class hierarchy runs the risk of being overused for too
many tasks:

1 data representation  (MyIntException,MyStringException)
2 exception gravity  (MyErrors, MyWarnings)
3 identifying specific errors (MyClassIsIllBecauseTheMoonIsFull)
4 identifying the throwing class(ios::failure, MyBase::failure)

Combined with exception specifications (throw clauses), I think this
proliferation of exception classes can be quite a maintenance problem,
unless the exception hierarchies are very carefully designed (using MI).

--
--------------------------------------------------------------------------
| Johan Bengtsson, Telia Research AB, Aurorum 6, S-951 75 Lulea, Sweden  |
| Johan.Bengtsson@lulea.trab.se; Voice:(+46)92075471; Fax:(+46)92075490  |
--------------------------------------------------------------------------