[comp.lang.c++] Novice question re: class derivation

richard@pantor.UUCP (Richard Sargent) (01/18/90)

We have encountered a problem designing a tree of classes, that I
hope someone out there can help resolve. I'm sure that it is just
a case of looking at the problem "the wrong way", but I can't see
the right way.

[Apology: I'll use some PC terminology in the description, but the
platform and hardware is not the problem, per se.]

We want to be able to provide a base class called Display, which has
derived classes for specific types of displays (such as Vista, Targa,
VGA, raster file, etc.). Our first pass results in the following
tree:

                  Display
                     |
      +--------+-----+--------+--------+
      |       |      |        |        |
    Vista   Targa   VGA   RasterFile  etc.

Now the problem comes when we try to code for the display device.
Ideally, we want to be able to use an instance of class Display,
so that our code is *written* device independent. We would like
to simply code "Display device" and apply operations to "device",
without concern for just which device it really is. The thinking
is the constructor for class Display is able to determine what
device it present and construct the appropriate class.

An alternative, but less elegant solution, is to code something
like the following, but I don't even know if this is valid:

        switch (inq_device_type()){
           case dv_VISTA:
              Vista device;
              break;
           case dv_TARGA:
              Targa device;
              break;
           case dv_VGA:
              VGA   device;
              break;
           .
           .
           .
           }


So the bottom line is: How can I set up a hierarchy of device
classes that will allow me to code (my main application, at least)
to be independent of the display device?

All help is appreciated. If any clarifications are needed, please
ask. Thank you.

Richard Sargent                   Internet: richard@pantor.UUCP
Systems Analyst                   UUCP:     ...!mnetor!becker!pantor!richard

ark@alice.UUCP (Andrew Koenig) (01/19/90)

In article <44.UUL1.3#5109@pantor.UUCP>, richard@pantor.UUCP (Richard Sargent) writes:

> So the bottom line is: How can I set up a hierarchy of device
> classes that will allow me to code (my main application, at least)
> to be independent of the display device?

I don't see anything wrong with what you've done so far.
You're missing just one little trick.  The idea is to never
use the object directly.  Rather, refer to it through a
pointer or reference to a base class object.

For example:

	class Display { /* ... */ };
	class ColorDisplay: public Display { /* ... */ };
	class HighResDisplay: public Display { /* ... */ };

Somewhere you have to make a decision on what kind of display you're
using.  Probably the most direct way to do it is something like this:

	Display* dp;

	if (cond1)
		dp = new ColorDisplay;
	else if (cond2)
		dp = new HighResDisplay;
	else /* ... */

Now you code all display operations in terms of pointer operations
on dp

	dp->clear();
	dp->draw(image);
	// and so on

When you're finally done, you can free it:

	delete dp;

but for this to work, your Display base class must have a virtual
destructor.

Perhaps a better way of structuring it, if you can, is to do all the
real work in a subroutine that takes the Display object as a
formal parameter.  Then the allocating and freeing are done in one
routine and everything else is done somewhere else.

If you do this, you can make that parameter a reference to a Display
rather than a pointer to a Display, which you may find more natural:

	void MyApplication(Display& d)
	{
		// ...
		d.clear();
		// and so on
	}

	main()
	{
		if (cond1) {
			ColorDisplay d;
			MyApplication(d);
		} else if (cond2) {
			// ...
		} // ...
	}

Now you don't have to allocate and free Display objects on the
free store -- they're local to the various branches of the `if'.

Hope this helps.
-- 
				--Andrew Koenig
				  ark@europa.att.com

shap@delrey.sgi.com (Jonathan Shapiro) (01/20/90)

In article <44.UUL1.3#5109@pantor.UUCP> richard@pantor.UUCP (Richard Sargent) writes:
>So the bottom line is: How can I set up a hierarchy of device
>classes that will allow me to code (my main application, at least)
>to be independent of the display device?
>
>Richard Sargent                   Internet: richard@pantor.UUCP
>Systems Analyst                   UUCP:     ...!mnetor!becker!pantor!richard

Right idea.  Wrong implementation.  By the time you hit the Device
constructor, you are essentially saying that you already are
constructing the appropriate kind.  What is needed is a single library
routine that determines the device type, allocates the right kind, and
returns a Device *:

Device *
getDevice()
{
   kind = determine_which_graphics_head();
   switch(kind) {
   case Targa:
       return new Targa;
   case Wombat:
       return new Wombat;
   ...
   }

   return 0;
}


Jonathan Shapiro
Silicon Graphics, Inc.

beard@ux1.lbl.gov (Patrick C Beard) (01/22/90)

In article <44.UUL1.3#5109@pantor.UUCP> richard@pantor.UUCP (Richard Sargent) writes:
#We have encountered a problem designing a tree of classes, that I
#hope someone out there can help resolve.
#
#We want to be able to provide a base class called Display, which has
#derived classes for specific types of displays (such as Vista, Targa,
#VGA, raster file, etc.). Our first pass results in the following
#tree:
#
#                  Display
#                     |
#      +--------+-----+--------+--------+
#      |       |      |        |        |
#    Vista   Targa   VGA   RasterFile  etc.
#

This seems like a perfectly reasonable approach to display design.
You would want virtual functions defined in the Display class for
every operation you would want to support across all of the devices.

#Now the problem comes when we try to code for the display device.
#Ideally, we want to be able to use an instance of class Display,
#so that our code is *written* device independent [sic].
#to simply code "Display device" and apply operations to "device",

Here is the case where a switch statement is appropriate:  you must decide
at run-time what particular instance to allocate.  This decision only
has to be made once, and is automated throughout the rest of your program
by the use of virtual functions.

Write a function of the form:

Display* AllocDisplay()
{
	Display *device = nil;	// default is a display we don't know about.
	switch (inq_device_type()){
	case dv_VISTA:
		device = new Vista;
		break;
	case dv_TARGA:
		device = new Targa;
		break;
	case dv_VGA:
		device = new VGA;
		break;
	/* etc... */
	}
	return device;
}

[Notice you have to use new to get the object to persist beyond the
AllocDisplay function.]

This isn't inelegant, it is the only way to do it.  Another approach might
be to use multiple inheritance and have the object you allocate inherit
methods for all devices, and knows internally what device it is talking to,
but I think that it would probably be a rat's nest to implement.  The
one drawback I see to the above method, is that if you add more display
types, you'll have to change this function to handle more types.  But the
changes will be isolated to this one function, all the rest of your code
only has to know about the base class.

I think this brings up an important issue:  use object oriented technology
where appropriate, standard functional programming where appropriate.

-------------------------------------------------------------------------------
-  Patrick Beard, Macintosh Programmer                        (beard@lbl.gov) -
-  Berkeley Systems, Inc.  ".......<dead air>.......Good day!" - Paul Harvey  -
-------------------------------------------------------------------------------