[comp.lang.c++] A valid use for MI?

warsaw@nlm.nih.gov (Barry A. Warsaw) (06/14/91)

I've just run across T.A. Cargill's article "Controversy: The Case
Against Multiple Inheritance in C++" in the Winter '91 issue of
Computing Systems (USENIX Journal, Vol 4, No.1).  Interesting article.
My general impression is that Cargill is not necessary saying MI is
totally useless in C++, just that there haven't been any really good
"real world" examples that couldn't be rewritten using SI and members.

In my current project, I've used MI for a very specific purpose and I
was wondering what you folks thought in regards to MI use in general
and Cargill's article.  Discussions about other approaches are
certainly welcome.

First some background. I am currently working on a set of classes
which implement an API for a full-text retrieval system.  There are
two parallel API developments going on simultaneously -- one is the
API which provides the functionality of the search engine directly to
the programmer (as an archive library -- see figure 1).  The other
provides the same public interface, but in a client/server environment
(see figure 2).  The idea is that a programmer can write an
application to the API once, and by simply recompiling and relinking
(i.e., no change to application code at all), the program can be part
of the same Un*x process as the search engine, or it can simply
communicate with a remote server on some other host:

 <process on same machine>
+-----------+---+--------+
|           |	:        |
| user      | A	: search |
| interface | P	: engine |
| applic.   | I	:        |
|           | 1	:        |
|           |  	:		 |
+-----------+---+--------+

figure 1. application and engine are linked together



  <process on machine 1>            <process on machine 2>
+-----------+---+--------+			 +--------+--------+
|			|  	:   	 |			 |		  :        |
| user		| A	: IPC	 |	network	 | IPC	  : search |
| interface	| P	: client |  =======> | server : engine |
| applic.	| I	:  		 |			 |		  :        |
|   		| 2	:  		 |			 |		  :        |
|   		| 	:  		 |			 |		  :        |
+-----------+---+ -------+			 +--------+--------+

figure 2. application communicates remotely with server


The requirement here is that API1 and API2 must have the same public
interface, which are the only methods the application programmer can
use.  The protected and private methods can be (and are) different for
the two API's. To ensure this, and to help the API developers debug
the interfaces (i.e., did I forget to implement a method in API1?),
both API1 and API2 inherit their public interfaces from a common
abstract class.  Here's a simplified example:

	// public abstract class interface
class Public_Database
{
public:
		// retrieve database name
	virtual string name( void ) = 0;
...
};


	// for standalone API (API1)
class Database : public Public_Database
{
public:
	string name( void ) { return( dbname ); };
	...

private:
	string dbname;	// name of database
};


	// for client API (API2)
class Database : public Public_Database
{
public:
	string name( void ) { return( handle->getDBname() ); };

private:
	IPC* handle;
};


As you can see, the user of the API need only instantiate a Database
class and call the name() method to get the name.  The difference
comes in which header files are #included and which library is linked
to, but the application source never changes.

Now, where does MI come into play?  Well, in API1, one of the classes
is contained in a list class, so it must multiply inherit both its
public interface and its "listable object" behavior so that it can be
managed by the list:


class List
{
	add( ListableObject* obj ) {...};
	...
};


class Public_Document
{
public:
		// get size of document
	virtual int size( void ) = 0;
	...
};


	// for standalone API (API1)
class Document : public Public_Document, public ListableObject
{
public:
	Document( List* list ) {
		...
		list->add( this );
		};

	int size( void ) { return( sz ); };

private:
	int sz;	// size of document
};


Now I'll be the first to admit that I'm not exactly a seasoned pro C++
programmer, but as near as I can tell MI in this (yes, real-world!)
example is conceptually clear and useful.  I'm not sure how this would
be otherwise implemented.  Certainly Public_Document must be inherited
to ensure the parallel public interface.  But it seems to me that the
Document class must also be a (not *have a*) ListableObj so that the
List class can manage it.

So, I hope this generates a bit of discussion. Perhaps this is a valid
use of MI, perhaps not?  Maybe someone has an alternative solution
which only uses SI -- or would that solution be "prohibitive" (*)
expensive?  Hopefully, my explanation of the situation makes sense...

(*) whatever that means. :-)

-Barry

NAME:  Barry A. Warsaw         INET: warsaw@nlm.nih.gov
TELE:  (301) 496-1936          UUCP: uunet!nlm.nih.gov!warsaw