🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: comp.std.c++
Article #2898

Re: Exceptions and dtor

#2898
From: Harald M. Mueller
Date: Thu, 07 Dec 1995 18:30
229 lines
7476 bytes
The other day I had a nice idea how one (i.e. the C++ standardization
committee - if it would still be willing to change the semantics of
the language) could solve all these problems with exceptions and
destructors. Before I tell you my idea, I'll tell you the two problems
with destructors and exceptions:

(a) A destructor must not throw an exception during stack unwinding; however,
there is no language-defined means of testing whether we are in stack-unwinding
or not, and hence all destructors must be written under the assumption that they
*might* be called during unwinding, hence they must not throw exceptions -
which some people find too restrictive (This has been discussed in this thread
quite thouroughly).

---  end of problem (a) ---

(b) A problem which I haven't seen discussed so far: How are you going to
handle an exception?!?!? Let me present my view:

- Throwing an exception means that some resource in a program is no longer
in a correct state (because memory was running out, because a parameter had
a wrong value, because of anything else).

- What can we do with this resource? In all cases where the correct value
cannot recomputed from some other information - i.e. in almost all cases!
because only very few pieces of information are stored redundantly -, we
must shut down (usually destroy) the resource. Consider e.g.:

struct Stack {
    struct XStack {	// exception class thrown if anything goes
			// wrong in a stack object
	XStack(const Stack& the_object_to_be_destroyed);
	// ...
      private:
	const Stack& s;
    };

    Stack() throw(Stack::XStack);
    void push(Something) throw(Stack::XStack);
    // ...
};

How would this class be used? E.g.:

f1() {
    try {
        Stack s1;
        ...
        f2(s1);
	...
    } catch (Stack::XStack& xs) {
	...(1)...
    }
}

f2(Stack& s1) throw(Stack::XStack) {
    try {
        Stack s2;
        ...
        f3(s1,s2);
    } catch (Stack::XStack& xs) {
	...(2)...
    }
}

f3(Stack& s1, Stack& s2) throw(Stack::XStack) {
    s1.push(s2.pop());
}

The question is: Which statements do we put at places (1) and (2)? What we
(might) want to do is:

	If the exception came from s2, we only want to execute (2), but f1()
	should continue;

	however, if s1 is also bad (which it tells us by throwing XStck),
	we also want to execute (1).

We therefore write:

	...(2)...:
	    ...deal with the problems from s2's shutdown...
	    if (& xs.s != & s2) throw;

and

	...(1)...:
	    ...deal with the problems from s2's shutdown...
	    if (& xs.s != & s1) throw;

Now, this is a very simple case. Consider e.g. the case where there is an
array of stacks in f2(): In this case we will have to search the array
to find out whether xs.s actually is a stack from the array (and hence we could
assume the exception handled if the array has vanished) or not (i.e. we
have to re-throw).

So what I would like to have is a possibility to stop stack-unwinding in
a destructor of an object that gets called during that stack-unwinding!

(BTW, does anyone have another idea or maybe concept what to write at (1) and
(2) in the above cases?)

---  end of problem (b) ---


Here's my proposal:

If a destructor has the (already allowed) form

	X::~X() try {
	    ...
	}
	catch (ExcType1) {
	    ...
	}
	catch (ExcType2) {
	    ...
	}
	etc.

then, IF it is called during stack unwinding (i.e. there is an "active
exception"), we immediately enter the corresponding handler - i.e. the
body of the constructor is NOT executed.
If it is not called during stack-unwinding, the current semantics
(execute body; if exception occurs, execute corresponding handler)
is not changed.

--- end of proposal ---


The solution to the problem of an exception occuring during stack
unwinding (problem (a)) now is simple: Write

	X::~X() try {
	    ...how to destroy object if no exception is active...
	    ...here we can write:
		if (problem) throw An_X_Cannot_Be_Destructed();
	}
	catch (Exctype& exc) {
    ...or...
	catch (...) {
	    ...how to destroy object if there is another exception present...
	    ...here we write:
		if (problem) throw An_X_Cannot_Be_Destructed();	// override exc
	    ...or...
		if (problem) throw;				// re-throw exc
	    ...or...
		if (problem) throw An_X_Cannot_Be_Destructed(exc); // make a
						// linked list from a new
						// exception and exc
	    ...or...
		if (problem) throw X(A(exc),y,asfg etc.)
						// throw some complex data
						// structure as exception which
						// tells everything about what
						// happened.
	}


The solution to problem (b) above is also simple, namely:

	X::~X() try {
	    destruct();
	}
	catch (Stack::XStack& xs) {
	    destruct();
	    if ( & xs.s != this) throw;
	}

where Stack::destruct() is the factored-out common code to destroy a
stack object, whether an exception occurred or not.


... a lot is still missing: How does this work with inheritance (single
and multiple)? When does stack unwinding actually end (of course, the
objects containing the "ending handler" must still be destroyed fully).
In the above form, weird effects can happen:

f() {
   { A a;
     g1(a);
     g2(a);
   }
   g3();
}

- if g1(a) throws an exception, the block containing a would be exited,
but if A::~A() now caught the exception, we would continue with g3() as
if nothing happened - an effect the programmer certainly would not suspect!
Maybe some syntax of the form

f() {
   try { A a;
     g1(a);
     g2(a);
   }
   g3();
}

might help (note: no handlers except the "implicit ones" in try-block); and a
definition that implicit handlers end the first (dynamically) surrounding
try-block - be it with or without handlers.

Other problems include destructors of dynamic variables (what are they supposed
to do - if occuring in a destructor which runs/runs not during stack unwinding);
code bloat (will many destructors have to contain two function calls to some
destruct() routine - one in the body, one in catch(...) ); etc.

Maybe the whole thing does not work. But I think that something in this
vein is necessary - if only to make exceptions more "object oriented" - i.e.
bound to objects=resources - and emancipate them from a pure control structure.

(Is there any reason I should work on this? or is C++ already defined? :-))

Harald M. Mueller

== -------------------------------------------------------------------
== Dr. Harald M. Mueller	                PSE EZE TNA1
== email: mueller@garwein.hai.siemens.co.at     Siemens AG Austria
== tel: +43-1-71711-5336	                Erdberger Laende 26
== fax: +43-1-71711-5425	                A-1030 Vienna/Austria
== -------------------------------------------------------------------



---
[ comp.std.c++ is moderated.  Submission address: std-c++@ncar.ucar.edu.
  Contact address: std-c++-request@ncar.ucar.edu.  The moderation policy
  is summarized in http://dogbert.lbl.gov/~matt/std-c++/policy.html. ]
Technical Details
Message-ID: <9512071429.AA03532@titania.hai.siemens.co.at>
Path: arc1-ca.pugleaf.net!howland.reston.ans.net!agate!agate!Matt

Show Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!howland.reston.ans.net!agate!agate!Matt
From: mueller@titania.hai.siemens.co.at (Harald M. Mueller)
Newsgroups: comp.std.c++
Subject: Re: Exceptions and dtor
Date: 07 Dec 1995 18:30:09 GMT
Organization: <none>
Approved: Matt@Austern
Message-ID: <9512071429.AA03532@titania.hai.siemens.co.at>
NNTP-Posting-Host: austern.hip.berkeley.edu