From: pauljo@microsoft.com (Paul Johns)
Subject: Re: Any inheritance experts out there? reprise
Date: 23 Dec 92 20:03:42 GMT
Organization: Microsoft:  Redmond, Washington, USA
Keywords: inheritance, virtual

I think you DON'T want to use a template here for the class.

Instead, an abstract base class "GENERIC_DEVICE" would contain
the pure virtual function declarations for the interface to the
class.

Each device would directly or indirectly derive from GENERIC_DEVICE 
and would provide definitions for all of the functions.

Similar devices (block vs. character) would have an additional
abstract base class put in.  And so forth.

So your inheritance hierarchy might look something like this:

	GENERIC_DEVICE <--- BLOCK_DEVICE <--- DISK_DRIVE <--- specific controller
		^-----------CHAR_DEVICE  <--- PRINTER    <--- specific printer

(You can tell from this derivation that I have MS-DOS background, can't
you?)

Note that the only non-abstract classes are the specific controller and
specific printer classes.  All the other classes have at least one pure
virtual function.  Therefore, the only objects that can be created are of 
the specific classes.

(BTW, if you have areas of commonality that span branches of the hierarchy, 
you may want to put that functionality in a class and use multiple 
inheritance.)

Your user would declare a pointer to the appropriate class and use it as
appropriate for that class.

For instance, if they were going to only do operations that were supported
on all devices, they'd declare "GENERIC_DEVICE *p;"  If they were doing
disk operations, they'd declare a pointer to a DISK_DRIVE.

They will only need the headers for the pointers they use.  They will
never need pointers to specific devices.  (One hopes....)

You'll need to provide a set of overloaded functions that will allocate 
an object for them and a way to select among the overloads.  For instance:

	enum DISK_DRIVE_ID { D_DISK_TYPE_1, ... };	// goes in USER.H

	DISK_DRIVE * MakeObject(DISK_DRIVE_ID d)  {
		switch (d)  {
			case D_DISK_TYPE_1:  return new DISK_TYPE_1;
			// ...
		}
	}

	enum PRINTER_ID { D_PRINTER_TYPE_1, ... };	// goes in USER.H

	PRINTER * MakeObject(PRINTER_ID d)  {
		switch (d)  {
			case D_PRINTER_TYPE_1:  return new PRINTER_TYPE_1;
		}
	};

I'm assuming that your specific device classes are called DISK_TYPE_1 and
PRINTER_TYPE_1.

You would only have enum/MakeObject pairs for the abstract base classes
which are immediate bases of actual device objects.  In our example, we
do NOT have pairs for the GENERIC_DEVICE, BLOCK_DEVICE, or CHAR_DEVICE
classes.

So your users would do this:

	#include <USER.H>	// contains all abstract base
				// class declarations, all enum decl's,
				// and prototypes for all MakeObject fns.
				// but **NOT** actual class decl's or
				// the definitions of MakeObject's

	// ...

		GENERIC_DEVICE *pgeneric = MakeObject(D_DISK_TYPE_1);
		BLOCK_DEVICE *pblock = MakeObject(D_DISK_TYPE_1);
		PRINTER *pprinter = MakeObject(D_PRINTER_TYPE_1);

		// the type of the enumerator selects the appropriate function

		// the pointer type returned might be to a derived class, but
		// that's fine

		pgeneric -> reset();	// only generic functions
		pblock -> readblock(...);	// generic and block functions
		pprinter -> formfeed();	// generic, character, and printer functions

When you added a new device, you would:

1.  Add the device ID to the appropriate enum in USER.H and add a case line to
	the corresponding MakeObject function in your private files.

2.  Add a class to handle the new device to your private files.

In addition, to add a new class of devices, you would:

1.  Add a new enum and MakeObject declaration in USER.H and add the
	MakeObject definition in your private files.

What do others think of this?

// Paul Johns
Newsgroups: comp.lang.c++