[comp.lang.c++] Handling trouble in C++

nagle@well.UUCP (John Nagle) (08/22/89)

     In C++, unlike C, a considerable fraction of the execution can
take place implicitly, within constructors and destructors, rather
than in explicitly called functions.  But there is no way for a
constructor or a destructor to signal an error condition to the
invoker.  Declarations aren't allowed to fail.  But constructors
often do such things as open files, windows, devices, or databases.
Such operations can fail.  What then?

     At present, we have a few options:

     1.  Panic and abort the program.  This is the Pascal solution,
	 and is one of the reasons nobody does serious work in 
	 standard Pascal.

     2.  Force the caller to do the opening operation separately,
	 then pass a handle for the open object to the class, rather
	 than letting the class handle the open itself.  This is
         the solution chosen for the streams package.  One of the
         major advantages of class declarations, the automatic
	 execution of the destructor upon scope exit, is lost
	 with this approach.

     3.  If the open fails, put the class instance into a "bad"
	 state, so that later operations on it produce some error
	 condition.  This works, but provides no guarantee that
	 the proper check will be made later.  Even if it is,
	 special effort must be made to save the information that
	 describes what went wrong during construction and then
	 to retrieve and analyze it later.

Well, that's where we are today.  What should be done?

					John Nagle

jordan@Morgan.COM (Jordan Hayes) (08/24/89)

John Nagle <nagle@well.UUCP> asks:

	What then?

Good question!  I think your answer (3) is closest to what I do:

     3.  If the open fails, put the class instance into a "bad"
	 state, so that later operations on it produce some error
	 condition.

This requires that all member functions call IsValid() or something
before using this and continuing, storing the last error message, etc.
and can make your code look ugly.  For example:

	#define	DEV		"/dev/dialer"
	#define	GOOD_TIME	"1 800 424 9090"

	Frob	*p;

	p = new Frob(DEV);
	if (p->status < 0) {
		Error("Frob(%s): %s\n", DEV, p->errStr);
		delete p;
		return;
	}
	p->Dial(GOOD_TIME);

	...

I asked a question a while back and got ZERO answers; it was along
the lines of "how do you get a constructor to return NULL?" so that
the above could be replaced with:

	if ((p = new Frob(DEV)) == NULL) {
		/* print error message */
		return;
	}
	p->Dial(GOOD_TIME);

Key to this: there must be no construction (note the explicit "delete"
in the 1st example) if it "fails" ...  Before C++, I used to write code
that sorta did this, i.e., in the following code, the "new Frob" gets
replaced with "MakeFrob":

Frob	*
MakeFrob(d)
	char	*d;
{
	int	fd;
	Frob	*p;

	if ((fd = open(d, O_RDWR)) < 0)
		return((Frob *)NULL);
	p = malloc(sizeof(Frob));
	p->fd = fd;
	return(p);
}

I miss it.

/jordan

cowan@marob.masa.com (John Cowan) (08/25/89)

In article <13262@well.UUCP> nagle@well.UUCP (John Nagle) writes:
>
>     In C++, unlike C, a considerable fraction of the execution can
>take place implicitly, within constructors and destructors, rather
>than in explicitly called functions.  But there is no way for a
>constructor or a destructor to signal an error condition to the
                                ^^^^^^^^^^^^^^^^^^^^^^^^^
>invoker.  Declarations aren't allowed to fail.  But constructors
>often do such things as open files, windows, devices, or databases.
>Such operations can fail.  What then?

This is part of the general C++ weakness in the area of signalling errors.
I understand some smart cookies at Bell Labs are thinking about this, but
so far without success, due to the very weak and non-portable handling of
non-local jumps in the underlying C.

What's really needed is something close to the Mesa signalling construct.
This is, IMHO, the slickest signal handling around.  Mesa allows you to
specify something like:
	catch {block1} using {block2}
where block1 is executed, and if a signal(code); statement appears within
block1, then block2 gets control.  (This is not actual Mesa syntax.)

Block2 is typically a switch statement which figures out which signal
has been sent.  If a signal is not handled by block2, we look further up
the stack for higher catchers.  Important point:  at this time the stack has
not been unwound, and it is still possible to return to block1!

The options of each case within block2 are:
	reject the signal, causing a search for higher catchers;
	return from the signal, which returns a value to the caller of signal()
as if nothing special had happened, just a function call;
	abort, which causes the successor of the catch construct to be
executed -- everything from block1 is wiped off the stack;
	retry, which causes everything from block1 to be wiped off the stack
and then block1 is restarted from the beginning.
-- 
Internet/Smail: cowan@marob.masa.com	Dumb: uunet!hombre!marob!cowan
Fidonet:  JOHN COWAN of 1:107/711	Magpie: JOHN COWAN, (212) 420-0527
		Charles li reis, nostre emperesdre magnes
		Set anz toz pleins at estet in Espagne.

nusbaum@meson.uucp (R. James Nusbaum) (08/25/89)

In article <13262@well.UUCP> nagle@well.UUCP (John Nagle) writes:
>
>     In C++, unlike C, a considerable fraction of the execution can
>take place implicitly, within constructors and destructors, rather
>than in explicitly called functions.  But there is no way for a
>constructor or a destructor to signal an error condition to the
>invoker.  Declarations aren't allowed to fail.  But constructors
>often do such things as open files, windows, devices, or databases.
>Such operations can fail.  What then?
>
>
>					John Nagle

One solution I used was to define a signal (SIGUSR1) as the 'constructor
failed' signal.  Then my constructors would raise this signal whenever
they failed while initializing an object on the stack.  The user could
then define a signal handler for this signal or just let the signal halt
the program.  Obviously this won't work on systems without signals and I
believe the method used by constructors to determine if the object was
on the stack was also not very portable.




Jim Nusbaum
Radiation Research Laboratory
Loma Linda University Medical Center
nusbaum%proton.uucp@ucrmath.ucr.edu

ttwang@polyslo.CalPoly.EDU (Thomas Wang) (08/25/89)

In article <13262@well.UUCP> nagle@well.UUCP (John Nagle) writes:

>In C++, unlike C, a considerable fraction of the execution can
>take place implicitly, within constructors and destructors, rather
>than in explicitly called functions.  But there is no way for a
>constructor or a destructor to signal an error condition to the
>invoker.

You have hit a major problem of C++, that it does not have an exception
handling mechanism.  The programmer has to handle errors in an ad-hoc
manner.  error checking in normal functions are tedious enough.  And
error checking in constructor and destructor are particularly nasty.

You can have a variable in a class that tells if its state is in a good
state.  But this will cause the deferred handling of errors and an increase
in complexity.

>                                       John Nagle

 -Thomas Wang ("I am, therefore I am."
                 - Akira               )

                                                     ttwang@polyslo.calpoly.edu

ttwang@polyslo.CalPoly.EDU (Thomas Wang) (08/25/89)

cowan@marob.masa.com (John Cowan) writes:

>This is part of the general C++ weakness in the area of signalling errors.
>I understand some smart cookies at Bell Labs are thinking about this, but
>so far without success, due to the very weak and non-portable handling of
>non-local jumps in the underlying C.

I would suggest to do a check on the failure variable after
every function call, and if a failure has occurred then goto the local
failure handling label.  There is always a local failure handling label,
even if the code at the label only calls some destructors, then returns
to the caller.

We cannot directly pop multiple stack frames, because the destructors will
not be called.  Therefore the only alternative is to pop one stack frame
at a time and to dutifully call the destructors.

In languages without destructors (e.g. Eiffel), we can directly pop multiple
stack frames.  Since C++ chooses the path of no garbage collection, there
will have to be some overhead for the failure handling mechanism.

We then have a choice to make.  Do we tell the function about the address
of the failure handler, or should we check for failure status after the
function call is completed.  It seems to me the second choice is better.

An important advantage of this scheme is that a C program calling C++
functions can use the failure handling mechanism.  The C program can just
test the failure variable after the C++ function call.  If the failure
variable is a register variable, the overhead should not be too great.

 -Thomas Wang ("I am, therefore I am."
                 - Akira               )

                                                     ttwang@polyslo.calpoly.edu

nagle@well.UUCP (John Nagle) (08/27/89)

In article <13789@polyslo.CalPoly.EDU> ttwang@polyslo.CalPoly.EDU (Thomas Wang) writes:
>cowan@marob.masa.com (John Cowan) writes:
>
>I would suggest to do a check on the failure variable after
>every function call, and if a failure has occurred then goto the local
>failure handling label.  There is always a local failure handling label,
>even if the code at the label only calls some destructors, then returns
>to the caller.

      No good.  What if failure is detected within a declaration, and
only some of the objects associated with that block have been created
at that point? The "local failure handling label" concept can't handle 
this, lacking information about exactly which constructors have been properly
instantiated.  

      An even worse case comes when a constructor of a base class fails
while construction of a derived class is underway.  Imagine instantiating
"menu" and having "window" fail at a lower level because too many
windows are open.

      When thinking about how to do this, it's worth remembering the
design criterion for the exception handling mechanism in Ada that
the overhead for the case in which no exceptions occur must be 
minimized, even if this makes exception handling slow.

				John Nagle

ttwang@polyslo.CalPoly.EDU (Thomas Wang) (08/29/89)

nagle@well.UUCP (John Nagle) writes:
>In article <13789@polyslo.CalPoly.EDU> ttwang@polyslo.CalPoly.EDU (Thomas Wang) writes:
>>I would suggest to do a check on the failure variable after
>>every function call, and if a failure has occurred then goto the local
>>failure handling label.  There is always a local failure handling label,
>>even if the code at the label only calls some destructors, then returns
>>to the caller.

>      No good.  What if failure is detected within a declaration, and
>only some of the objects associated with that block have been created
>at that point? The "local failure handling label" concept can't handle 
>this, lacking information about exactly which constructors have been properly
>instantiated.  

This is easily handled.  If there is only one local failure handler, you
can have a local variable that tells the location of the error.

a::a()
{
  int location = 0;
  do_something();
  location = 1;
  do_something2();
  location = 2;
....
}

Beside, if the language can implement one local failure handler, why can't
it have multiple local failure handlers?  This would solve the problem
nicely.

>      An even worse case comes when a constructor of a base class fails
>while construction of a derived class is underway.  Imagine instantiating
>"menu" and having "window" fail at a lower level because too many
>windows are open.

This is not a problem either.  The failure will be passed upward toward
the main() function, until the program is cleanly aborted.  The failure
from 'window' is passed to 'menu' after 'window' cleans up its structure.
The 'menu' then handles the failure, cleans up its structure, and pass
the failure upward...

>      When thinking about how to do this, it's worth remembering the
>design criterion for the exception handling mechanism in Ada that
>the overhead for the case in which no exceptions occur must be 
>minimized, even if this makes exception handling slow.

The destructor of C++ presents big problems to the implementors of
exception handling.  When a failure occurred, all the destructors for
local variables must be called up to the point where we have a failure
handler available.  We have two choices:  We can let destructors to be
handled locally, and check failure after every function call.  We can
also implement a local variable registry to handle the calling of destructors
in a global fashion.  I suspect the overhead for checking failure after
every function call is actualy smaller.

The paper "Exception Handling Without Language Extensions" in the 1988
Usenix C++ Conference describes a local variable registry implementation
outside the language itself.  So this method is at least known in the
computing world.

>				John Nagle

 -Thomas Wang (Ah so desu ka!)

                                                     ttwang@polyslo.calpoly.edu