[comp.lang.c++] Runtime type checking for base-to-derived conversion

gyro@kestrel.edu (Scott Layson Burson) (05/11/91)

In article <1991May07.151417.3386@lut.ac.uk> J.Schmidt@lut.ac.uk (J Scmidt) writes:
>I hope if anybody could help me with this problem:
>
>I have defined the class Ordinary. Objects of this class are interconnected
>by pointers which carry much of their semantics (not simple lists or sets). 
>The class has operations navigating along these pointers. In the future
>other people will want to enhance the object in different
>ways (attaching various attributes, graphical appearance etc.) but the
>basic navigation will still be needed. The most natural way (I think)
>is to derive classes like Advanced from the Ordinary. As Advanced* can
>be converted to Ordinary*, the network of pointers can be constructed
>without problems. The problem is that the basic navigational operations
>will return a pointer or a reference to Ordinary and there is no way
>to convert them to Advanced*.

I have implemented a solution to this problem that I really like.
It's a fair amount of work, but I think it works well both
semantically and efficiency-wise.  Basically:

0) Use what has been called a "hidden pointer" class structure for
Ordinary and its subclasses.  In this approach, there are two classes
for each class the client actually sees; the outer class has a single
member variable, which is a pointer to the inner class.  So there
would be both `Ordinary', which is all the client would ever see, and,
say, `Ordinary_impl'; then `Advanced' would inherit from `Ordinary',
and `Advanced_impl' from `Ordinary_impl'.  Since the sole member
variable of `Ordinary' and all its derived classes is always of type
`Ordinary*', we still have the same problem, but we're going to use
the two-level class structure to encapsulate it and thus hide it from
the client code.

1) Define a class of objects to serve as the runtime representation of
Ordinary and each of its subclasses.  `Ordinary_impl' defines a
virtual function `my_class()' that returns a pointer to one of these
objects (this way it's not necessary for each instance to have such a
pointer).  (`Ordinary' defines a nonvirtual member function of the
same name that invokes that member function on the object it points
to.)  The class object holds several pieces of information, including
a pointer to the class object for the base class of this class and an
integer, the class number.  The class `object_class' itself has
several static member variables, one of which is a square bit table
indexed by class number, called the inheritance table.

2) For each class in the hierarchy, declare a global (or class static)
variable that will hold the class object for that class.  Arrange that
the initialization of that object a) assign its class number and b)
add a row to the inheritance table showing which classes this class is
derived from (by copying the base class's row and setting an
additional bit).

3) For each class other than Ordinary, define a constructor in that
class that takes one argument of type `Ordinary'.  This constructor
gets the class object from its argument; gets the class number; and
looks in the bit table to see if the object really is of the type
being constructed (or one of its derived types).  If so, it constructs
the object of the desired type using the pointer inside the argument;
otherwise it traps into the debugger (or whatever).

----

As you can see, it really is a lot of work -- I was actually taking
the approach of generating the C++ declarations programatically from a
much simpler description, which made it tractable.  But the result is
a lovely combination of compile-time and run-time type checking,
giving the best features of both: run-time type checks are inserted
automatically (since the constructor from `Ordinary' is treated as a
user-defined conversion), but only where they're actually needed: only
when a convrsion down the hierarchy, from a base class to a derived
class, is attempted.

Oh yes, the above description assumes single inheritance.  The
generalization to multiple inheritance with virtual bases is
difficult, but I think I know how to do it, though I've never
implemented my putative solution.

-- Scott
Gyro@Reasoning.COM