In article <SO*****************@newsread1.news.atl.earthlink. net>
Joseph Dionne <jd*****@hotmail.com> wrote:
Here is a simple example of how you can achieve polymorphisms. ...
This code will not work on the Data General Eclipse, because:
[Snippage below no longer marked; I retained only the amount of
code needed to show where it breaks. Note that this removed some
error checking. I also re-ordered the code so that the problem
will be more obvious.]
#include <stdio.h>
#include <stdlib.h>
void intMyFunc(int ii)
{
printf("int parameter virtual function (%d)\n",ii);
}
enum {
VINT,
VSTRING
};
int main(int argc,char **argv)
{
int ii = 456;
myFunc(VINT,&ii);
Here, the machine will convert &ii (a word pointer) to a byte pointer,
by shifting it left one bit.
}
void myFunc(int varg,void *arg)
{
union
{
void *vv;
int *ii;
} virt_arg;
virt_arg.vv = arg;
Note that this will store a byte pointer in the union, even though
the "int *ii" member will be assumed to hold a word pointer.
switch(varg)
{
case VINT : intMyFunc(*virt_arg.ii); break;
Here, the machine will follow the word pointer -- but virt_arg.ii
actually contains a byte pointer. The result is a runtime trap
(mapped to a "segmentation fault" in DG/UX), as the value is not
a legal word-address.
We could make the call to intMyFunc work by doing this instead:
case VINT : intMyFunc(*(int *)virt_arg.vv); break;
but if we are going to do that, we might as well get rid of the
union entirely, and just write:
case VINT : intMyFunc(*(int *)arg); break;
In either case, this will cause the compiler to insert the needed
assembly-level instruction to shift the byte pointer right one bit,
converting it back to a word pointer, before following the resulting
pointer.
}
}
In general, I prefer to simulate C++-like "virtual functions" using
function pointers. There are at least two simple and sensible
approaches (or three in more-limited situations), with somewhat
different tradeoffs.
Consider the following data structure, out of a "rogue"-like game:
struct monster {
char *name; /* e.g., "troll" or "umber hulk" */
int level; /* dungeon level at which it normally appears */
int armor_class; /* its native armor class */
int initial_hitpoints; /* and number of hit points */
... and so on ...
};
Now, some monsters have "special" attacks, like a "floating eye"
whose gaze can freeze the player (if he is not blind). These are
often implemented by the kind of switch/case shown above, but it
may be simpler in some situations to include this in the structure:
struct monster {
char *name; /* e.g., "troll" or "umber hulk" */
int level; /* dungeon level at which it normally appears */
void (*special_attack)(struct monster *, struct player *);
...
Here we simply set the special_attack member of an instance of the
data structure to point to the function that implements the special
attack. (We can also set the pointer to NULL to indicate "no
special attack", or just force the programmer to provide a no-op
function for that case.)
If there is only one "virtual function" for a given data structure,
this method -- embedding the function pointer directly in the
structure -- is almost always the way to go. In many programs,
however, we find that some "polymorphic" data structure needs
to be connected to many "virtual functions". In this case, it
is often better, at least space-wise, to use a second level of
indirection:
struct monster_funcs {
void (*special_attack)(struct monster *, struct player *);
... more function pointers ...
};
struct monster {
char *name; /* e.g., "troll" or "umber hulk" */
int level; /* dungeon level at which it normally appears */
struct monster_funcs *ops; /* operations this kind of monster does */
...
Now, instead of calling:
(*monster->special_attack)(monster, player);
at the appropriate point in the game, we might do this instead:
(*monster->ops->special_attack)(monster, player);
Although it conserves space, and makes it easier to set up the data
structure in the first place, this method has two (relatively minor)
drawbacks:
- It takes an extra indirection to find the function to call
(usually one more instruction per function-call). This is
generally slightly slower than having the pointer directly
in the data structure. If, after the program works, performance
testing shows this to be a problem, you can always "hoist up"
any key pointer(s), so it is not something you should worry
about when first writing the code.
- It makes it impossible to take an existing data structure and
mutate "just one op". For instance, a floating eye might itself
be blind-able, negating its special attack. Instead of having
a flag ("this floating eye has been blinded, that one has not"),
if we have the op pointer directly in the data structure --
rather than in one data structure shared by all instances of
that monster -- we can just zap out the op at that point:
message("You blinded the eye!");
m->special_attack = NULL; /* no more freezing gaze for THIS eye */
Of course, you can handle this second problem the same way as you
can handle the first: keep the "operations table", but hoist that
particular op up into the data structure.
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
email: forget about it
http://web.torek.net/torek/index.html
Reading email is like searching for food in the garbage, thanks to spammers.