Article View: net.lang.c++
Article #26Re: Exception handling in C++
From: John Rose
Date: Wed, 26 Feb 1986 22:06
Date: Wed, 26 Feb 1986 22:06
172 lines
6743 bytes
6743 bytes
(Note: I can't tell if I'm getting onto the net. No one commented on
my previous posting on lambda expressions and expression-valued blocks in C
(net.lang.c), so I couldn't tell if I'd gotten through. If there is
no comment on this paper too, could some kind moderator-type please
e-mail me a pat on the back saying it was at least broadcasted?)
> 1. Will this implementation work in general, or have I forgotten
> something? Are there some dire consequences of using setjmp/longjmp in
> this fashion? Note that destructors will not be called when a block is
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> exited by doing a RAISE.
This is a bug in longjmp() which is easily fixed with a little conspiracy
with the C++ compiler. A guarantee of de-initialization which only applies
to local exits is not much of a guarantee. For example, I would like to
write an open-file abstraction which ensures a close at the end of its use.
I use a stdio FILE instead of a stream to make explicit the resource management:
class open_file {
FILE* fp;
public:
open_file(char *name, char *mode = "r")
{ fp = fopen(name, mode); }
~open_file()
{ fclose(fp); }
operator FILE*()
{ return fp; }
};
void load_file(char *name)
{ open_file in(name);
// do something with the stream, e.g.:
extern lispval read(FILE *), eval(lispval);
lispval l;
while (l = read(in)) (void)eval(l);
// ...and when open_file goes out of scope, the right things happen
}
Implementationally, an "open_file" is just a FILE pointer, with guaranteed
de-initialization tacked on. The guarantee should have the force of a Lisp
UNWIND-PROTECT, or else the open_file abstraction is much less usable.
And if we don't have a garbage collector to manage storage behind our backs,
free store management requires such guarantees also.
(What other examples of guaranteed deinitialization are there? Do they
require UNWIND-PROTECTs? And is the use of non-local exits limited to
a few types of application, such as user interfaces and language interpreters?
Can you give an example of an application where non-local exits are inconceivable,
or where we don't care about non-local deinitialization?)
Here is some C code which would be a plausible translation of the above,
including UNWIND-PROTECTs. You may have seen this technique in Betz's
XLISP, which is written in C.
/* #include <longjmp.h>: */
struct catch {
void (*catch_fn)();
void *catch_arg;
struct catch *catch_prev;
};
extern struct catch _thread;
/* end include */
struct open_file {
FILE* fp;
};
void deinit_open_file(this) void *this;
{ fclose(((struct open_file *)this)->fp); }
void load_file(name) char *name;
{ struct open_file in;
extern lispval read(), eval();
lispval l;
// Establish a catch block:
struct catch deinit_in;
deinit_in.catch_fn = deinit_open_file;
deinit_in.catch_arg = (void *)∈
deinit_in.catch_prev = _thread;
_thread = &deinit_in;
in.fp = fopen(name, "r");
while (l = read(in.fp)) (void)eval(l);
_thread = _thread->catch_prev;
fclose(in.fp); // inline exp. of deinit_open_file
}
Clearly, there is an expense in establishing the catch block: Four
extra accesses to a new structure on the stack, and two accesses to an
external variable are needed for each creation/deletion of a protected
object; an out-of-line delete function must also be compiled. But this
expense is not incurred for classes without guaranteed deinitialization,
and perhaps it is reasonable to declare that any class which needs guaranteed
deinit. should need it consistently for non-local exits. Also, particular
architectures such as the Vax allow cheap establishment of unwind handlers
(one write to the stack frame).
A compromise: After the signature is an outline of a library class which
provides unwind protection, and some applications, including file closing.
The idea is that objects of class "catch" are ALWAYS deinitialized, even
by longjmp(), and so if you include such an object as one of your members,
or as a local variable, you can get a hook on stack unwinds.
----------------------------------------------------------
John R. Rose, Thinking Machines Corporation, Cambridge, MA
245 First St., Cambridge, MA 02142 (617) 876-1111 X270
rose@think.arpa ihnp4!think!rose
class catch {
static catch* thread = 0;
catch* prev;
virtual void spring()
{ /* your code here */ }
friend void _unwind_catches(void *);
public:
catch()
{ prev = thread; thread = this; auto_check(this); }
~catch()
{ thread = prev; spring(); }
};
void _unwind_catches(void *upto) // Normally only called on
{ for (catch *cur; (void *)(cur = thread) < upto; ) {
thread = cur->prev; // same as ~catch().
cur->spring();
}
}
/* Just before longjmp unwinds each stack frame,
* it calls _unwind_catches() with the address of that frame.
*/
class generic_catch : catch {
void (*catch_fn)();
void *catch_arg;
void spring()
{ catch_fn(catch_arg); }
public:
generic_catch(void (*fn)(), void *arg = 0)
{ catch_fn = fn; catch_arg = arg; }
}
class catch_restart : catch {
jmp_buf from_the_top; // HACK! To jump out of the unwind.
inline catch_restart()
{ flag = setjmp(from_the_top); }
virtual int restart_p()
{ return 1; } // Predicate for when to restart.
void spring()
{ if (restart_p()) longjmp(from_the_top, 1); }
public:
int flag;
}
// Here is class open_file:
class open_file {
friend file_catch;
class file_catch : catch {
void spring() // depends on being 1st in struct:
{ fclose(((open_file *)this)->fp); }
} cb;
FILE* fp;
public:
open_file(char *name, char *mode = "r");
// No destructor: handled by member cb.
};
--
----------------------------------------------------------
John R. Rose, Thinking Machines Corporation, Cambridge, MA
245 First St., Cambridge, MA 02142 (617) 876-1111 X270
rose@think.arpa ihnp4!think!rose
Technical Details
Message-ID:
<4431@think.ARPA>Path:
arc1-ca.pugleaf.net!utzoo!watmath!clyde!burl!ulysses!allegra!mit-eddie!think!roseReferences:
<51@cecil.UUCP>Show Full Headers
Complete Article Headers
Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Posting-Version: version B 2.10.2 9/17/84; site think.ARPA Path: arc1-ca.pugleaf.net!utzoo!watmath!clyde!burl!ulysses!allegra!mit-eddie!think!rose From: rose@think.ARPA (John Rose) Newsgroups: net.lang.c++ Subject: Re: Exception handling in C++ Message-ID: <4431@think.ARPA> Date: Wed, 26-Feb-86 17:06:53 EST Article-I.D.: think.4431 Posted: Wed Feb 26 17:06:53 1986 Date-Received: Fri, 28-Feb-86 21:57:11 EST References: <51@cecil.UUCP> Reply-To: rose@think.UUCP (John Rose) Organization: Thinking Machines, Cambridge, MA Lines: 172 Keywords: exceptions, unwind-protect