[comp.lang.c++] Exception Handling

keith@nih-csl.UUCP (keith gorlen) (02/07/88)

In article <3117@okstate.UUCP>, norman@a.cs.okstate.edu (Norman Graham) writes:
> in article , keith@nih-csl.UUCP (keith gorlen) says:
> 
> > My inclination is to ignore the OOPS exception handling code as much
> > as possible, and replace it when exception handling is incorporated
> > into C++.
> 
> I tried desperately to design a suitable (ie. simple, transparent, etc)
> exception handling scheme for a project I'm involed with ( >80000 lines 
> of C source in >450 files).  We wanted to use a stack of exception frames
> and we would have, except for one problem... we couldn't guarentee that the
> exception frame stack would be poped on each function return.

Actually, popping the exception frame on each function return is the
easy part.  You just make exception frames instances of a class that
has a destructor, and have the destructor update the exception frame
stack.

But don't get excited -- the real problem is that when an exception
occurs and you want to jump out to an enclosing exception frame,
there's no way to get the destructors of all the objects local to
blocks that are going out of scope called without help from the
compiler.

Bjarne has mentioned in several papers and talks that he's working on
exception handling for C++.  An additional problem is that the C++
exception handling mechanism should work in situations where you have
C and C++ functions calling one another, and the C functions don't
understand C++ exception handling.


-- 
	Keith Gorlen			phone: (301) 496-5363
	Building 12A, Room 2017		uucp: uunet!ncifcrf.gov!nih-csl!keith
	National Institutes of Health	Internet: keith%nih-csl@ncifcrf.gov
	Bethesda, MD 20892

shapiro@blueberry.inria.fr (Marc Shapiro) (03/04/88)

In article <306@nih-csl.UUCP> keith@nih-csl.UUCP (keith gorlen) writes:
>But don't get excited -- the real problem is that when an exception
>occurs and you want to jump out to an enclosing exception frame,
>there's no way to get the destructors of all the objects local to
>blocks that are going out of scope called without help from the
>compiler.

Well, it can be fudged.  We have a macro package for exceptions
(thanks to Dima Abrossimov) which does the right thing.  This is how
it works: macro-expand a "raise exception" into "fudge the stack;
return".  Cfront sees the return and generates calls to destructors of
local variables; however you have fudged the stack so that the return
jumps into your exception handler instead of continuing in sequence.

I'm not saying it's the right way.  Proper compiler support is a much
better idea.


						Marc Shapiro

INRIA, B.P. 105, 78153 Le Chesnay Cedex, France.  Tel.: +33 (1) 39-63-53-25
e-mail: shapiro@inria.inria.fr or: ...!mcvax!inria!shapiro

						Marc Shapiro

INRIA, B.P. 105, 78153 Le Chesnay Cedex, France.  Tel.: +33 (1) 39-63-53-25
e-mail: shapiro@inria.inria.fr or: ...!mcvax!inria!shapiro

scs@itivax.UUCP (Steve C. Simmons) (10/10/88)

In article <4370@polya.Stanford.EDU> shap@polya.Stanford.EDU (Jonathan S. Shapiro) writes:
>[[an excellent summary of useing overloaded new to implement
>  garbage collection in exception processing.  Heavily summarized:]]
>Exception handlers define a scope.
>Think of the heap as a temporally scoped object, where the
>instantiation of a "catch" scope logically partitions the heap into
>stuff older than that catch and stuff newer than that catch.
>Now overload all instances of New() within that scope so that the
>run-time system maintains, along with the scope information, a spare
>pointer to every object allocated with New() within that scope, along
>with a pointer to the destructor function.  Thus, associated with each
>heap scope we have a list of objects and their destructors.
>
>Now, when an error is raised or a "throw" is executed, we locate the
>scope that is thrown to and destroy all of the stack-instantiated
>objects. We then destroy dynamically allocated things in the clobbered
>scopes in an undefined order.

Deallocating the stack-instantiated objects (whether alloca or auto)
should be no issue.  Heap cleanup is the hard part.

>Now I know that there are lots and lots of problems here.  I also know
>that I don't know a lot about exception handling.  Can someone give me
>a non-flaming list of problems with this idea so that I can think on it
>further?  The big problem I see is that there is no good ordering
>constraint on the destructions.

It's not clear why order is a problem.  Could you explain?

As for other problems: I've seen PASCALs with an operator that
marks the current size of the heap.  You can later call a
reset operator that resets the heap to that size.  This reclaims
all the allocated space, but does not restore the structure of
the unreset heap.  The problem comes when you get complex lists.
Half the list was built before the mark, half afterwards.  You're
willing to give up the data newed after the mark, but your old
data is now possibly corrupt -- it may have pointers into the
area you just freed.  How do you fix it?  At least in the PASCALs
I've seen, you don't.

Now that I think about it, current C programs have the same problem
with malloc/free, don't they?

Might it be possible to create reference counts to items created
with new()?  It would add some expense to the new function (tho not
a lot), but possibly a lot of expense to the rest of the compiler
to maintain those.  It would remove any problems I can think of with
order of destruction, and garbage collection would start to look
suspiciously like fsck.
-- 
Steve Simmons		...!umix!itivax!scs
Industrial Technology Institute, Ann Arbor, MI.
"You can't get here from here."

shap@polya.Stanford.EDU (Jonathan S. Shapiro) (10/11/88)

In article <301@itivax.UUCP> scs@itivax.UUCP (Steve C. Simmons) writes:
>In article <4370@polya.Stanford.EDU> shap@polya.Stanford.EDU (Jonathan S. Shapiro) writes:
>>... The big problem I see is that there is no good ordering
>>constraint on the destructions.
>
>It's not clear why order is a problem.  Could you explain?

The reason ordering is important is that there is a metasemantics to
object creation order. Consider a linked list.  Destroying the head
might be implemented to destroy the rest, but I might be referring to
that object someplace else.

Consider the following problem that this might lead me to, which I see
as the really hard problem here:

I instantiate an exception scope, then I create an object (call it
Counter) that I am going to use as a counter for the number of Foo
objects I have.  The Foo constructor appropriately increments the
count, the destructor decrements it.

If I blow away Counter before I blow away all the Foos I could
(potentially) get into trouble (though it will be hard to notice,
since the decrement operators are now happening on logically freed
memory).

The example is contrived.  Now imagine that Counter is some object
that keeps track of the number of references to itself and deletes
itself when appropriate.  Such objects are very useful, and could get
one into serious trouble here. This example is all too real.

Now the metasemantics of the ordering is not readily expressible in
the language.  How should this problem be addressed?

Jon

scs@itivax.UUCP (Steve C. Simmons) (10/11/88)

In article <4383@polya.Stanford.EDU> shap@polya.Stanford.EDU (Jonathan S. Shapiro) writes:
[[a nice explaination to my question about why destructor ordering
  is important in garbage collection scope/exception handling]]
>The reason ordering is important is that there is a metasemantics to
>object creation order. Consider a linked list.  Destroying the head
>might be implemented to destroy the rest, but I might be referring to
>that object someplace else.
>....
>Now the metasemantics of the ordering is not readily expressible in
>the language.  How should this problem be addressed?

Perhaps our problem is that we are failing to properly treat collections
of objects.  If an object is contained within a larger object, this is
something we need to consider when defining the constructor/destructor
for both of the objects.  Taking an example:

We have a class string which contains a reference count.  When refcount
is zero, it calls its destructor.  We have a class stringlist which is
a linked list of strings.  Should the use of a string in the linked list
comprise a reference to it?  If you say yes, then as long as the string
is on the list it will never be released.  If you say no, we have to deal
with the case of a list containing a destroyed object.

In either case, it's something we *should* be accounting for in
designing the list object.  Should the list object should be smart
about the objects it contains, and be able to remove items from the
list when the list is the only referent?  Clumsy, but maybe appropriate
for some objects.  More general is a list object that notices when
something it references has been deallocated and cleans up the list.
But just how do we detect that in a reliable fashion?

Hmm.  Now we've got an example where the cleanup problems are
independant of exception processing.  Could we be so bold to say
that there are no cleanup problems that could not arise independant
of exception processing?  I dunno.

This is a very slipperly slope we're talking about.  Pretty much everything
I can think of seems to seriously bend the rules of data hiding.  Sigh.
-- 
Steve Simmons		...!umix!itivax!scs
Industrial Technology Institute, Ann Arbor, MI.
"You can't get here from here."

ttwang@polyslo.CalPoly.EDU (Thomas Wang) (03/01/89)

I posted because the mail bounced.
I am a CSC student planing on doing a thesis on C++ exception handling.
What is a rough estimate of the time when C++ would have exception handling
build-in?  Also in what syntax form would it appear in?

Thank you much.

 -Thomas Wang ("I am, therefore I am."
                 - Akira               )
                                                     ttwang@polyslo.calpoly.edu

davidm@uunet.UU.NET (David S. Masterson) (09/22/90)

Can somebody provide an example of using exception handling (as defined in
E&S) with constructor failures?  I think I understand the definitions, but a
piece of annotated, example code would certainly help.

Thanx
--
====================================================================
David Masterson					Consilium, Inc.
uunet!cimshop!davidm				Mtn. View, CA  94043
====================================================================
"If someone thinks they know what I said, then I didn't say it!"

mjv@objects.mv.com (Michael J. Vilot) (10/03/90)

David Masterson asked for an example of exception handling with constructor
failures:

  int f() throw(X);
  int g() throw(Y);
  
  class A {
    int a_part;
  public:
    A() { a_part = f(); }
    ...
  };
  
  class B : public A {
    int b_part;
  public:
    B() /* A() invoked */ { b_part = g(); }
    ...
  };
  
  main()
  {
    try {
      B b;
    }
    catch(X) {
      // we get here if f() failed while building b.a_part
      // b.b_part not attempted
    }
    catch(Y) {
      // we get here if g() failed while building b.b_part
      // note that if `a_part' had been of a class with a 
      // destructor, then that destructor would have been executed
    }
  }
  
  [My aplogies to everyone -- my earlier posting re-sent his question,
   instead of my response.]

--
Mike Vilot,  ObjectWare Inc, Nashua NH
mjv@objects.mv.com  (UUCP:  ...!decvax!zinn!objects!mjv)

davidm@uunet.UU.NET (David S. Masterson) (10/06/90)

In article <958@zinn.MV.COM> mjv@objects.mv.com (Michael J. Vilot) writes:

   [I] asked for an example of exception handling with constructor
   failures:

     int f() throw(X);
     int g() throw(Y);

     class A {
       int a_part;
     public:
       A() { a_part = f(); }
       ...
     };

     class B : public A {
       int b_part;
     public:
       B() /* A() invoked */ { b_part = g(); }
       ...
     };

     main()
     {
       try {
	 B b;
       }
       catch(X) {
	 // we get here if f() failed while building b.a_part
	 // b.b_part not attempted
       }
       catch(Y) {
	 // we get here if g() failed while building b.b_part
	 // note that if `a_part' had been of a class with a 
	 // destructor, then that destructor would have been executed
       }
     }

Would it be valid to add a call to another function (say myf()) at the end of
main() where its defined as below?

	int myf() {
	    try {
		B b;
	    }
	    catch(X) {
		// something different than previous catch(X)
	    }
	    catch(Y) {
		// something different than previous catch(Y)
	    }
	}

Point is, is it valid to reset how exceptions are processed?
--
====================================================================
David Masterson					Consilium, Inc.
uunet!cimshop!davidm				Mtn. View, CA  94043
====================================================================
"If someone thinks they know what I said, then I didn't say it!"

mjv@objects.mv.com (Michael J. Vilot) (10/11/90)

David Masterson asks:

> Would it be valid to add a call to another function (say myf()) at the end of
> main() ... Point is, is it valid to reset how exceptions are processed?

The short answer is: Yes.
Handlers for the same exception attached to different blocks are distinct.
So, a handler for catching X's thrown from one block need not do the same
thing as a handler for catching them in some other context.

Note, however, two points:

  1.  For the set of handlers attached to the _same_ block, ``control is transferred
  to the nearest handler of an appropriate type ... the handlers for a try-block are 
  tried in order of appearance.  It is an error to place a handler for a base class
  ahead of a handler for its derived class since that would ensure that the handler
  for the derived class would never be invoked.'' [E&S, Ch 15]
  
  2.  ``An exception is considered handled upon entry to a handler.'' That is, it
  takes an explicit action to throw (or re-throw) in order to propagate an exception
  beyond a matching handler.
  
Hope this helps,

--
Mike Vilot,  ObjectWare Inc, Nashua NH
mjv@objects.mv.com  (UUCP:  ...!decvax!zinn!objects!mjv)

sdm@cs.brown.edu (Scott Meyers) (01/23/91)

I'm trying to understand the proposed exception handling mechanism, and
since I don't have access to an implementation, I have a couple of
questions about exceptions, especially as regards constructors.  Consider:

    class NoMoreMemory { ... };    // one class of exceptions
    class OtherException { ... };  // another class of exceptions

    // a class where the constructor may raise exceptions
    class Foo {
      public:
        Foo() throw (NoMoreMemeory, OtherException);
    };

    // a global object whose construction may raise exceptions
    Foo globalFoo;    

    // a function with local objects that may raise exceptions
    void f()
    {
      Foo local1;

      try {
        Foo local2;
      }
      catch (NoMoreMemory) { /* do something */ }
      catch (OtherException) { /* do something else */ }
    }

Is it true that:

    1.  There is no way to catch exceptions raised during the construction
        of globalFoo except via calls to set_unexpected?

    2.  f cannot catch exceptions raised during the construction of local1
        because that declaration isn't in a try block?

    3.  Any function can be declared to raise an exception, including
        operators new and delete and class destructors?

    4.  Assuming that it is legal for destructors to raise exceptions,
        you may get different behavior from such a destructor depending on
        whether it is called "normally" or as part of the stack unwinding
        process during the handling of another exception (ARM 15.6.1c)?

Also, speaking of implementations, what's the state of g++ exception
handling these days -- does it implement something akin to that described
in chapter 15 of the ARM?

Thanks,

Scott


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