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