[comp.lang.c++] real example of MI

carroll@cbnewsm.att.com (martin.d.carroll) (01/01/91)

Dear net people,

A little while back, Bjarne posted a request for people to submit
real examples of multiple inheritance (MI) in C++.  Tom Cargill 
has been making the same request for a while.  Here is my contribution.  

The following code implements an abstract Map interface via an underlying
Skiplist data structure.  The use of MI appears in the following:

    template <class T, class U> 
    class Map_via_skiplist : private Skiplist<T>, public Map<T,U> { /* ... */ };

Now, before you scream, "But one of those inheritances is private, and
so could just as well have been implemented using composition!" let me
explain.  There are two reasons why I use private inheritance instead
of composition:

	1. The Map-via-Skiplist class has to have access to more than 
	the public interface of the Skiplist class.  (That is, it has
	to have access to more than what I wish to provide "direct"
	public clients of Skiplist.)  

	Typically, when a data structure is intended to be used as the 
	underlying data structure for other types, it is convenient/natural 
	to make the protected interface of that data structure available 
	for private inheritance.

	2. The Skiplist class defines a "virtual constructor" which the 
	Map_via_skiplist class must override.  Thus, this example satisfies 
	Bjarne's and Tom's criterion that each of the inheritances must 
	override a virtual function.

Notice, incidentally, that the private inheritance method is no less
inefficient than the composition method: only a single extra virtual
function call per insert operation, which is more than overshadowed
by the cost of actually doing the insertion.

Here is the code.  I've included the definitions of only those functions
which are of interest for the example.  The following actually compiles
under 3.0 cfront!  Enjoy, and have a happy new year,

martin carroll
carroll@mozart.att.com

////////////////////////////////////////////////////////////////////
// Real example of multiple inheritance in C++.  
// Here is the overall class hierarchy:
//
//template <class T> 
//class Skiplist_node { /* ... */ };
//
//template <class T> 
//class Skiplist { /* ... */ };
//
//template <class T, class U> 
//class Map { /* ... */ };
//
//template <class T, class U> 
//class Map_via_skiplist_node : public Skiplist_node<T> { /* ... */ };
//
//template <class T, class U> 
//class Map_via_skiplist : private Skiplist<T>, public Map<T,U> { /* ... */ };
//
////////////////////////////////////////////////////////////////////

// Defined below.
template <class T> 
class Skiplist;

// The type of node in a Skiplist.
template <class T> 
class Skiplist_node {
	friend Skiplist<T>;
protected:
	// This node's key.
	T key;

	// Construct a Skiplist node with the given number of levels and key.
	Skiplist_node(int nlevels, const T& key);

	// Destructor must be virtual, since constructor below is virtual.
	virtual ~Skiplist_node();

	// ... more stuff
};

template <class T> 
class Skiplist {
public:
	// Insert a key into the Skiplist.  Return true if the key was present.
	// Side-effect: sets the_node to the inserted or present node.
	int insert(const T& key);

	// Return true if the key is present.
	// Side-effect: sets the_node to the node containing the key, or 0 if 
	// the key not is present.
	int contains(const T& key) const;

	// Construct a Skiplist that can hold at most maxsize keys.
	Skiplist(int maxsize);

	// Destructor.
	~Skiplist();
protected:
	// Virtual constructor.  Should return a new instance of the
	// type of node for this Skiplist.
	virtual Skiplist_node<T>* newnode(int nlevels, const T& key) const;

	// Used by derived class.  Holds pointer to node inserted or located
	// by most recent insert or contains operation.
	Skiplist_node<T> *the_node;

	// ... more stuff
};

template <class T>
Skiplist_node<T>* 
Skiplist<T>::newnode(int nlevels, const T& key) const {

	// The type of node for Skiplist is Skiplist_node.
	return new Skiplist_node<T>(nlevels, key);
}

template <class T, class U> 
class Map {
public:
	// Insert the given <key,value> pair into the Map.
	// Return true if the key was present (in which case
	// its value is updated).
	virtual int insert(const T& key, const U& value) = 0;

	// Return true if there is a <key,value> pair with
	// the given key.
	virtual int contains(const T& key) const = 0;

	// If the given key is present, return true and set
	// value to the associated value, otherwise return
	// false without changing value.
	virtual int valueof(const T& key, U& value) const = 0;
};

// Defined below.
template <class T, class U> 
class Map_via_skiplist;

// The type of node in a Map implemented via a Skiplist.
template <class T, class U> 
class Map_via_skiplist_node : 
	public Skiplist_node<T> {
	friend Map_via_skiplist<T,U>;
protected:
	// The value associated with this node's key.
	U value;

	// Construct a Skiplist node with nlevels and the given <key,value> 
	// pair.
	Map_via_skiplist_node(int nlevels, const T& key, const U& value_);

	// Override the virtual destructor.
	~Map_via_skiplist_node();

	// ... more stuff
};

template <class T, class U> 
Map_via_skiplist_node<T,U>::Map_via_skiplist_node(int nlevels, const T& key, const U& value_) : 

	// Construct the Skiplist_node, then initialize the value.
	Skiplist_node<T>(nlevels, key), value(value_) {
}

template <class T, class U> 
Map_via_skiplist_node<T,U>::~Map_via_skiplist_node() {
	// ... anything necessary to destroy a Map_via_skiplist_node.
}

// An implementation of the Map protocol using Skiplists.
template <class T, class U> 
class Map_via_skiplist : 
	private Skiplist<T>, 
	public Map<T,U> {
public:
	// Construct a Map that can hold at most maxsize <key,value> pairs.
	Map_via_skiplist(int maxsize);

	// Implement the Map protocol.
	int insert(const T& key, const U& value);
	int contains(const T& key) const;
	int valueof(const T& key, U& value) const;
private:
	// Cast Skiplist::the_node to the appropriate type for derived class.
	Map_via_skiplist_node<T,U> *The_node() const { 
		return (Map_via_skiplist_node<T,U>*)the_node; 
	}

	// Override the virtual constructor.
	Skiplist_node<T>* newnode(int nlevels, const T& key) const;

	// See the implementation of insert below.
	const U* pvalue;

	// ... more stuff
};

template <class T, class U>
int
Map_via_skiplist<T,U>::contains(const T& key) const { 

	// To see if a key is in the Map, simply look in the Skiplist.
	return Skiplist<T>::contains(key); 
}

template <class T, class U>
int
Map_via_skiplist<T,U>::insert(const T& key, const U& value) {

	// To insert a <key,value> pair into the Map,
	// do the following:
	//	1. Squirrel away the given value for the virtual constructor.
	//	2. Insert the key into the Skiplist.
	//	3. If the key was present, update its value.

	pvalue = &value;  			// squirrel away value
	if (Skiplist<T>::insert(key)) {  	// if key was already there
		The_node()->value = value;  	// update its value
		return 1;
	}
	return 0;
}

template <class T, class U>
Skiplist_node<T>* 
Map_via_skiplist<T,U>::newnode(int nlevels, const T& key) const { 

	// Construct a Map_via_skiplist_node with the given key,
	// and the value just squirreled away by insert.

	return new Map_via_skiplist_node<T,U>(nlevels, key, *pvalue); 
}

template <class T, class U>
int
Map_via_skiplist<T,U>::valueof(const T& key, U& value) {

	// To find the value associated with a given key, do the following:
	//	1. Look in the Skiplist for the key.
	//	2. If the key was present, look in the node for the value.

	if (Skiplist<T>::contains(key)) {
		value = The_node()->value;
		return 1;
	}
	return 0;
}

mat@mole-end.UUCP (Mark A Terribile) (01/02/91)

In article <1990Dec31.182239.23486@cbnewsm.att.com>, carroll@cbnewsm.att.com (martin.d.carroll) writes:
> A little while back, Bjarne posted a request for people to submit
> real examples of multiple inheritance (MI) in C++.  Tom Cargill 
> has been making the same request for a while.  Here is my contribution.  
> 
> The following code implements an abstract Map interface via an underlying
> Skiplist data structure.  The use of MI appears in the following:
> 
>     template <class T, class U> 
>     class Map_via_skiplist : private Skiplist<T>, public Map<T,U> { /* ... */ };

I encourage EVERYONE to look this over very, very carefully.  Templates and
MI work together here in a particularly potent way.  If the algorithms for
the Skiplist are particularly interesting and involve data structures of
their own, it might even make sense to derive  Skiplist<T>  from a
Skiplist_manager whose algorithms can be shared instead of being recompiled
for every template-type expansion.  (A sufficiently smart compiler might
be able to determine what member functions can be common to all  Skiplist<T> ,
but it would have to be a VERY smart compiler).

There is something of a problem here:

> template <class T, class U> 
> class Map_via_skiplist_node : 
> 	public Skiplist_node<T> {
> 	friend Map_via_skiplist<T,U>;
> protected:
> 	// The value associated with this node's key.
> 	U value;
> 
> 	// Construct a Skiplist node with nlevels and the given <key,value> 
> 	// pair.
> 	Map_via_skiplist_node(int nlevels, const T& key, const U& value);

The problem is that we have to provide an already-constructed  U  to the
constructor, and we have to have a default constructor for the  U that is
a member, or this code will not work.  There's no way to say `substitute
here the parameters to the constructors for U .'  I suspect that doing this
using C++'s present constructor syntax would be a horrific problem, especially
as regards ambiguities.  All is not lost; we can write

	typedef Map_via_skiplist_node< Name, Widget > mvns_Widget;

	new mvns_Widget( levs, Name( name_arg_1, name_arg_2 ),
					Widget( Widget_arg_1, Widget_arg_2 ) );

but that seems a bit clumsy, as if we can do better.

Finally, if this is compiled using 3.0, wouldn't it be possible to make
some (or all but one) of these templates for classes internal to just one
of them, so that the program namespace is kept a bit cleaner?
 
If I wore a hat I would take it off to anyone who can debug a compiler with
all these different levels of names expanding to names with various, sundry,
and generally sundered meanings!  In the feral and furry world of templates,
we are asking an awful lot of the compiler.
-- 

 (This man's opinions are his own.)
 From mole-end				Mark Terribile