Topic: Question on Template
Author: eeyore@wet.UUCP (Ilya Vinarsky)
Date: 9 Aug 93 22:39:43 GMT Raw View
Newsgroups: comp.std.c++, comp.lang.c++
Subject: Object-oriented Approach to Error Handling.
Summary: A mechanism for error handling is proposed.
References:
Followup-To:
Distribution: usa
Organization: Wetware Diversions, San Francisco
Keywords:
Hello everyone!
I am a student, formerly in the City College of San Francisco, now
transferring to U.C. Berkeley. This spring, I wrote the following paper,
but didn't find anyone who has the time and qualifications to read and
criticize it. Then, following a teacher's advice, I posted it on Internet,
in comp.lang.c++ and got a reply from Germany, with a recommendation to read
about a language called Beta. So I did; it is an exciting new language
Unfortunately, I didn't manage to find anything about error handling in it.
So, could someone please read the following article and either post the
comments on the net or mail them to eeyore@wet.uucp.
Thanx in advance.
An Object-Oriented Approach to Exception Handling
This document proposes "intercepting handlers", a control structure
for programming languages that is primarily intended for handling
exceptions, such as division by zero, but is much more general. This
structure is very different from the exception processing features of the
existing languages, and I believe that it is much better suited to writing
software, both academic and commercial. This structure has to be
incorporated into future languages; the subset I managed to implement
using preprocessor and inline assembly language in C is probably too simple
to be useful.
Briefly, the idea is that even though in a modular program a calling
routine is not supposed to know the inner workings of a called one, there
are situations when it should. Of these, intercepting exceptions is most
important. If a routine that raises an exception, it may have a default
handler, which is called unless the calling routine knows more about the
circumstances under which the exception occurred and knows better what to
do in this situation, or the routine that calls the calling routine does.
Under certain circumstances a floating-point division function might print
"Real division by zero" and terminate; the Gaussian elimination procedure
which uses floating-point division may find "Matrix is singular" more
appropriate. In turn, the engineering circuit analysis tutorial program
which uses Gaussian elimination may want to intercept the exception and
conclude that the circuit is at resonance, which is not an error in the
first place. This looks very much like inheritance for classes in C++,
where a derived class knows more than a base class and can override the
base class's methods - this is why I called the proposed control structure
object-oriented.
The idea is derived from a few assumptions about well-structured
programs that to me seem quite self-evident. It is possible to divide
routines in a program into levels, if only informally. In a package that
controls industrial test equipment , the routines that deal directly with
hardware are below the routines that implement test procedures, which, in
turn, are below the ones that determine whether to pass or to reject a
product. Higher-level routines usually call same-level or lower-level
ones, and not the other way around. Besides, in well-structured programs
the difference in levels between a calling and a called routine is usually
very small. If the test equipment package, written in C, sets a hardware
register from main(), one can hardly call it well-structured.
Nevertheless, the difference in levels is not always zero or one; this is
especially true for program libraries. Text-mode user interface libraries
for PCs usually feature, among other things, a set of routines that
manipulate the display screen directly, a windowing system, and a menuing
system based on it. Most applications programs that use the library
utilize them all.
To keep the program modular, a higher-level routine should not worry
about possible exceptions in lower-level ones. This means that were it to
happen, such an exception would have to be handled on the spot, usually by
printing an error message and stopping execution. However, it may well be
that the lower-level routine was called via a middle-level one, which knows
better what to do. In a memory allocation library there may be two
routines: _rmalloc, which allocates real memory and prints an error message
and stops the execution if too little is left, and malloc, which tries to
allocate real memory, and if there isn't enough, agrees to virtual one.
Even though the programmer would probably want malloc to call _rmalloc, it
is impossible. The usual solution to this problem, to have a function
_rmalloc1 that allocates the memory but doesn't respond to the exception
and to make _rmalloc a "shell", is not terribly elegant, and sometimes even
difficultBV1. For this and for other reasons I think that a mechanism that
would allow a calling procedure to override certain features of a called
one would be welcomed by many programmers.
Exception handling is not the only reason why this mechanism would be
useful. A program that uses a library procedure that prints a file might
want to print its own header at the top of each page. Optimization may be
another justification. When calling a routine that spends a great deal of
time checking whether its parameters are an easy special case, it would
help to have a way to tell it that yes, they are. Moreover, if this
routine is called only once in the program, and if the linker is really
smart, it can throw away the unnecessary code! But the most important
potential use for intercepting handlers, apart from exceptions, comes from
routines that take another routines as parameters, for example, binary
search and solving a system of differential equations by Runge-Kutta's
method.
In a way, all this document is proposing is a feature that would allow
the lower-level routines to call higher-level code. Programming languages
have always had a way to do this, from Algol 60, where one could write
"sigma (n, 1, 100, 1/n^3)", to C++, where the constructor of a derived
class calls the constructor of the base class before it does anything else.
The most common way to do it in languages like C is to pass a pointer to
function as a parameter to a procedure. However, it is not always
appropriate; in all my programs that call binary search function, the
comparison routine is logically not a module on its own but a part of the
calling routine, and often needs its local variables. Of the many language
features that are considered too unsafe and were, for example, not included
into Ada, pointers to functions and function parameters stand out. So,
just like classes, inheritance and virtual functions in C++ eliminate the
need for pointers to functions, intercepting handlers would rid a language
from function parameters.
Before going into the details of the proposed control structure, I
would like to give a few code examples in a C++-like language and their
possible implementation on a processor with separate frame pointer and
stack pointer.
1 extern exception zerodiv_ex ();// From stddef.h
2 exception singular_ex ();
3 // Solve a system of linear equations using Gaussian elimination.
4 void solvelineqn (int N, const float A[], const float b[], float x[]) :
singular_ex
5 {
6 try
7 {
8 // Elimination without bothering to pivot.
9 for (int i=0; i<N; i++)
10 for (int j=i+1; j<N; j++)
11 {
12 float ratio1 = A[j*N+i]/A[i*N+i];
13 b[j] -= b[i]*ratio1;
14 for (int k=N-1; k>=i; k--)
15 A[j*N+k] -= A[i*N+k]*ratio1;
16 }
17 }
18 intercept
19 {
20 zerodiv_ex (): {singular_ex (); }
21 singular_ex (): {cerr << "Singular matrix cannot be
inverted.\n"; abort (); }
22 }
23 // Back-substitution.
24 ...
25 }
This is how it might be implemented:
Line 1,2: An exception doesn't "belong" to a routine, it's an object
in its own right. If it hasn't been this way, floating-point addition,
multiplication and division would have to raise different kinds of
overflow. Also, an exception may have parameters, even if these particular
two don't.
Line 4: A caller of solvelineqn may intercept singular_ex. It may
not override any other exception that may arise inside solvelineqn. So,
even though the calling routine may change some aspects of solvelineqn, it
cannot fashion it into something totally different.
Line 6: Save the current frame pointer in static memory. Save
zerodiv_ex in automatic memory; if it is 0, set it to the address of line
20. Save singular_ex in automatic memory; if it is 0, set it to the
address of line 21.
Line 12: A call is made to the library floating-point division
routine, as usual. Although it would probably be more elegant to implement
intercepting handlers with implicit function parameters with default values
than to mess around with global variables, this would mean half a dozen
extra words - handlers for overflow, underflow, division by zero and
whatnot - passed to the floating-point division routine. This is clearly
unacceptable.
The floating-point division routine saves zerodiv_ex. If it is 0, it
is set to the default handler, the one that prints "Real division by zero".
If there was a division by zero, a call is made to the location pointed to
by zerodiv_ex. Before it returns, the division routine restores zerodiv_ex
so that, for example, the bignum division routine which recycles this
exception would not be deceived into thinking that its handler has been
intercepted.
Line 20: Here the fun begins! The frame pointer register is saved on
stack and is set to whatever was saved in line 6, so the handler will have
the access to the local variables of solvelineqn. Inside the handler, the
frame pointer would have no relation to the stack pointer - a situation
quite unusual in a C program but not unbearable. The function that
allocates memory on stack, alloca, standard in many implementations of C,
has precisely this effect. This particular handler, however, does not do
anything besides calling singular_ex. Just before return, like in a
regular procedure, the frame pointer is restored from stack.
Line 21: Pretty much the same as line 22.
Line 22: Restore zerodiv_ex, so that if the main program wants to
divide a number by 0, the error message would make sense. Also restore
singular_ex.
The next example is a little bit more complicated. It has to do with
the not too unusual situation when an exception is caused by the
limitations of a particular implementation of an algorithm, not by
something logically impossible. A call to QuickSort on a peculiar array
can result in stack overflow. In this situation, it is reasonable to call
an algorithm that is less prone to this condition, such as HeapSort. If
the data are truly anomalous, the exception may be raised once again.
Then, a recursive call wouldn't make much sense; instead, the default
handler, the one that prints an error message and quits, should probably be
called. Still, if the calling procedure has a different opinion, it may
intercept the exception the second time around.
1 extern exception overflow_ex (); // From stddef.h
2 extern exception underflow_ex (); // From stddef.h
3 // Return the hypothenuse of a right triangle with legs a and b.
4 double hypot (double a, double b) : overflow_ex, underflow_ex
5 {
6 try
7 return sqrt (a*a+b*b);
8 intercept
9 {
10 // Try to compute the hypotenuse for the two numbers' mantissas
and
11 // adjust for the exponents separately. This is done so
12 // hypot(HUGE_VAL/10,HUGE_VAL/7) would return the correct
result.
13 overflow_ex () static:
14 {
15 double a_mant, b_mant, r_mant;
16 int a_exp, b_exp, r_mant;
17 a_mant = frexp (a, &a_exp);
18 b_mant = frexp (b, &b_exp);
19 if (a_exp>b_exp)
20 {r_exp = a_exp; b_mant = ldexp (b_mant, b_exp-a_exp); }
21 else
22 {r_exp = b_exp; a_mant = ldexp (a_mant, a_exp-b_exp); }
23 // If ldexp raises overflow_ex(), it is handled as usual.
This can
24 // happen in hypot(HUGE_VAL,HUGE_VAL).
25 r_mant = sqrt (a_mant*a_mant + b_mant*b_mant);
26 return ldexp (r_mant, r_exp);
27 }
28 underflow_ex () static:
29 {
30 ...// Similar code.
31 }
32 }
33 }
The modifier "static" means "you can't override me but you can
override what's inside of me".
Line 6: Save the frame pointer and the values of overflow_ex and
underflow_ex. Set overflow_ex to the address of line 13, underflow_ex to
the address of line 27.
Line 13: Restore overflow_ex (set it to whatever it was before line
6).
Line 28: Restore underflow_ex.
Line 32 and after line 7: Restore underflow_ex and overflow_ex.
The third example is typical of how intercepting handlers may replace
routine parameters:
1 extern exception compare_hdl (int idx, int &cmp);
2 extern int bsearch (int left, int right) : compare_hdl = 0;
3 ...
4 try
5 int name_index = bsearch (0, table_size-1);
6 intercept
7 compare_hdl (int idx, int &cmp) :
8 cmp = strcmp (sought_name, table[idx].name);
9 ...
The variables idx and cmp are allocated in the routine's stack frame,
since a handler doesn't have a stack frame on its own. They are pulled off
stack and put into their slots in line 7. This is similar to the situation
in C++, where the methods of a derived class can access "protected" data
members of the base class.
What should happen if an exception occurs inside a non-static handler?
If things are even worse than the programmer could anticipate, and the data
the handler uses can be garbled themselves. An
index-outside-of-array's-boundaries handler may itself access an array with
a nonsensical index. It seems to me, it would make sense to call the
lower-level, probably the default handler. A handler can also find that
this particular exception is none of its business. For example, a routine
that opens a file for reading can raise an exception if the file doesn't
exist. This exception will have a default handler that plainly prints a
message and stops. A fancier version of this routine may override this
handler; should the exception occur, the higher-level handler would
inquire whether the file is supposed to be on a floppy, and if so, ask the
user to insert a floppy with this file. But what to do if the file is
supposed to be on a hard disk? Again, it would be most reasonable to call
the handler in the plain routine. The above implementation does not allow
anything like this, but it would not be hard to extend it - overflow_ex
would have to be a double-ented queue instead of a single pointer to code.
When entering a try block, the address of the current non-static handler
would be appended to the bottom of the queue; when exiting, it would be
taken off. When an exception is raised, the topmost handler address is
called. When entering a handler, the topmost element would be removed, so
that a call to overflow_ex would invoke the second highest handler. When a
handler is being exited from, the topmost handler address is restored. The
whole technique is quite similar to C++, where the first thing a
constructor of a derived class does is a call to the constructor of its
base class, except that the binding is dynamic. Although at this point it
is hardly certain whether it is worth it and whether there is a better
solution. I implemented a subset of it in C++ using preprocessor and
inline assembler; however, my ideas about this generalized technique are
too preliminary for this document.
In general, the people who design programming languages, operating
systems, libraries and processors take two approaches to handling errors.
One is to have the language environment or the operating system spit out a
terse message and to stop execution once something funny occurs. This may
or may not be fine for an excercise in a college programming class, but it
is clearly unacceptable for something like Microsoft Word. If a commercial
word processor is asked to load a file that is too big, the program should
at the very least save what the user was working on before exiting, or
better still, say "File too large" and not exit at all. The other
approach, taken by the designers of C, is to have an environment where
everything is allowed. In Microsoft C, for example, writing to a file
opened for reading produces no error message. This means that the
programmer has to check whether memory has really been allocated after
every call to malloc and realloc, and whether files have really been opened
by fopen. Many programmers, including myself, often don't bother to do
this; it is possible, then, that our programs have latent bugs that would
manifest themselves in a less permissive environment. The C approach,
therefore, makes our programs less safe. It seems to me that intercepting
handlers are the best of both worlds, since they let the programmer content
with the system printing an error message if the program runs out of memory
be content, and permits the one who isn't to do something else.
The proposed control structure is the exact opposite of the scheme in
all the languages I know about that have exception handling features: PL/I,
Ada, Eiffel, Clu, proposed mechanism for C++ etc. I am having a hard time,
though, figuring out what scheme Common Lisp Condition System adheres to.
In these languages an exception is processed at the top only if it is not
processed at the bottom, and if it's not processed on any level, the
language environment prints a message. Thus, even though the language
environment, as exemplified by the operator "+", is conceptually below the
application program, in this particular case it is above - an annoying if
trivial inconsistency. There is one situation, however, for which the Ada
scheme is better. If a fatal error occurs at the bottom of the call stack,
there should be clean-up and calling destructors for local variables. This
process should logically proceed from the bottom to the top, and in an
interactive environment, such as the read-eval-print loop in Lisp, at some
point it should stop. An analogous situation, which is even easier to
visualize, can happen if a program with a menu-based interface is in the
middle of a deeply-nested menu, and the user presses "Escape" several
times. At this point, I don't know how to reconcile this with intercepting
handlers.
One can think of a language where there would be no difference between
a class and a routine, the way in Simula there was no difference between a
class and a coroutine. A routine would have "private" and "protected"
local variables, a prologue and an epilogue similar to a constructor and
destructor for a class. It would be possible for one routine to inherit
from another and to stick its own code into specified holes. Although at
this point it is too early to assert that such a language is possible, let
alone useful, it may well be, and intercepting handlers would certainly be
one of its most frequently used constructs.
Ilya Vinarsky.
Author: ucrudy@socrates.umd.edu (Rudy Zulkarnain)
Date: Sun, 8 Aug 1993 05:24:28 GMT Raw View
Hi..
I am *very* new to C++,
I am trying to use template.
Can someone tell me why can't I compile the code below using g++?
I am using g++ 2.3.3 and I got the following error message.
as0: Error: t.cc, line 25: .ent must precede the definition of the symbol s_temp
as0: Error: t.cc, line 25: Conflicting definition of symbol s_temp
as0: Error: t.cc, line 25: .ent must precede the definition of the symbol s_temp
as0: Error: t.cc, line 25: Conflicting definition of symbol s_temp
However, I can managed to compile the code below using Borland C++ 3.1.
Thanks in advance,
Rudy Zulkarnain
----- Code --------- Cut Here -----------
#include <iostream.h>
class myint {
public:
void myboy();
};
void myint::myboy()
{
cout << "MYINT: IN SAMPLETEMPLATE" << endl;
}
class mylong {
public:
void myboy();
};
void mylong::myboy()
{
cout << "MYLONG: IN SAMPLETEMPLATE" << endl;
}
template<class T>
void s_temp(T x)
{
x.myboy();
}
main()
{
myint x;
mylong a;
s_temp(x);
s_temp(a);
}
Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Mon, 9 Aug 1993 02:23:04 GMT Raw View
ucrudy@socrates.umd.edu (Rudy Zulkarnain) writes:
>Hi..
>
>I am *very* new to C++,
>I am trying to use template.
>Can someone tell me why can't I compile the code below using g++?
>I am using g++ 2.3.3 and I got the following error message.
>
>as0: Error: t.cc, line 25: .ent must precede the definition of the symbol s_temp
>as0: Error: t.cc, line 25: Conflicting definition of symbol s_temp
>as0: Error: t.cc, line 25: .ent must precede the definition of the symbol s_temp
>as0: Error: t.cc, line 25: Conflicting definition of symbol s_temp
Anytime that you get an error message from the _assembler_, rather
than from the linker or compiler, it indicates a bug in compiler
(presuming you're not using inline asm statements, of course).
g++ 2.3.3 was fairly buggy especially with regard to templates,
and I would not advise someone very new to C++ to use templates
with g++ 2.3.3. Version 2.4.5 is better, although I don't
no whether it fixes this particular bug, and it would still
probably be a good idea to avoid templates for a while,
until you have a fair bit of experience with C++.
If you do get the latest version of g++, and find that the
bug is still there, you should report a bug to the g++ maintainers
(see the gcc documentation for how to do this).
--
Fergus Henderson fjh@munta.cs.mu.OZ.AU