MI Is not Mission Impossible

Introduction

‘Too many cooks spoil the broth’ says the old adage. This is very much true for multiple inheritance (MI). This article enumerates the roadblocks that occur when using MI in C++ and provides solutions to them. I really don’t expect you to be an expert in inheritance, but you must be aware of it and why it’s used. Your knowledge of C++ must also encompass virtual functions and the like because I won’t explain any of those concepts here. Before you dive into the controversies surrounding MI, first take a quick refresher.

What Is Multiple Inheritance?

Inheritance is one facet of Object-Oriented Design that a programmer cannot afford to ignore. It is possible that inheritance may not suit some scenarios, but the great majority of OOD coders rely on inheritance to propagate data unobtrusively through classes or structures. It is important to know that there are different kinds of inheritance. These are:

  • Single Inheritance
  • Multi-level Inheritance
  • Hierarchical Inheritance
  • Multiple Inheritance

You need to be concerned with this last kind of inheritance. Multiple inheritance is simply the derivation of a class from two or more base classes. This means that a class derived from several classes gets the best of all those worlds. Here’s a little code for the uninitiated.

//First Base Class.
class CBase1
{

};

//Second Base Class.
class CBase2
{

};

//Derived Class inherits from CBase1 and CBase2.
class CHybrid : protected CBase1, protected CBase2
{

};

Pictorially, one could depict this inheritance hierarchy as:

Many programmers dispute the use of MI in their programs and truly believe that there are other ways to circumvent situations where MI might seem more pertinent. However, it must be pointed out that Multiple Inheritance is the only natural way to go when you want to create a class that is a hybrid of the characteristics of two or more classes.

What’s the Problem?

The very concept of MI has come under fire due to several issues in implementation. Most of these issues are related to ambiguity in some form or the other. Although initially frustrating, experienced programmers have come to adopt ways and means through which these problems can be tackled.

1.) The Diamond problem

The Diamond problem is a standard issue that crops up with most programmers who decide to implement MI. Consider the following code snippet:

//Super Base Class.
//Characterises all mythical characters in Greek literature.
class CGreekMyth
{
   public:
      virtual char* Description(void);
};


//Class derives from CGreekMyth.
//Characterises all mythical Animals from Greece.
class CAnimalMyth : public CGreekMyth
{
   public:
      char* Description(void);

};


//Class derives from CGreekMyth.
//Characterises all flying Greek Myths.
class CFlyingMyth : public CGreekMyth
{
   public:
      char* Description(void);
};


//Derives from CAnimalMyth and CFlyingMyth.
//Charcterises a Chimera.
class CChimera : public CAnimalMyth, public CFlyingMyth
{
   public:
      char* Description(void);
};

If you are familiar with Greek mythology (or have played Age Of Mythology), this will make perfect sense to you. If you aren’t too imaginative, let me explain. CGreekMyth is a class that implements the functionality to represent any character from Greek mythology. CAnimalMyth is a class derived from CGreekMyth that represents all mythical creatures from Greece on a broad scale (examples include Cerberus, Pegasus, Chimera, and so forth).

CFlyingMyth also inherits from CGreekMyth and implements the characteristics of all flying myths from Greece (for example, Chimera, Icarus, Pegasus and so on). Finally, you create a class to represent a particular type of flying creature from Greek mythology, the Chimera (a CPegasus class would also need to inherit from CFlyingMyth and CAnimalMyth).

If I were to visualize this inheritance, I might see it like this:

Notice the diamond shape formed by the arrows in the diagram. CGreekMyth contains a virtual function char* Description(void) that is overridden by CAnimalMyth, CFlyingMyth, and CChimera. Now here’s the problem. Suppose that I want to do this:

CGreekMyth        *pGM;
CAnimalMyth       amObj;
CFlyingMyth       fmObj;
CChimera          chObj;

pGM   = &amObj;    //OK.
pGM   = &fmObj;    //OK.
pGM   = &chObj;    //Will not compile.

Most compilers will generate an error message similar to this one: CGreekMyth is an ambiguous base of CChimera. This is because CChimera is derived from both CAnimalMyth and CFlyingMyth. Both these classes are also derived from the Super Base Class CGreekMyth. So, effectively, CChimera has two copies of CGreekMyth inherited through each of its parents. These copies are generally known as sub-objects. When converting from the indirectly derived class, CChimera, to the Super Base Class, CGreekMyth, the compiler doesn’t know which sub-object is to be used. This type of problem usually occurs when you try to use the Super Base class polymorphically. You might want to create a pointer of type super base and then use that pointer to do a number of things, such as invoke overridden member functions, for example.

The solution

The Diamond problem is solved by using a known keyword in C++: virtual. The base classes of the class that multiply inherits must be virtually derived from the Super Base class. In the example, CAnimalMyth and CFlyingMyth must be virtual base classes of CChimera.

class CAnimalMyth : public virtual CGreekMyth
{
   ...
};


Class CFlyingMyth : public virtual CGreekMyth
{
   ...
};

By using the keyword virtual, both CAnimalMyth and CFlyingMyth are forced to share a single copy of their parent, CGreekMyth. This resolves the ambiguity that would normally be faced for an object of type CChimera.

2.) The Ambiguity problem

I read an example on MSDN, http://msdn2.microsoft.com/en-us/library/ms973861.aspx, that showed why multiple inheritance suffered within the .NET Framework. I’ll put forth this example (as stated at the link provided).

Suppose you have a class named Dog with a virtual function Bark(). Now, say that you derive two classes named Hound and Puppy from Dog. Hound and Puppy override the Bark() method to sound like a ‘Howl’ and a ‘Yelp’ respectively. Finally, you create a class named BabyBasset that multiply inherits the characteristics of both Hound and Puppy. The BabyBasset class does not override Bark(). Now, if you create a BabyBasset object and call its Bark() Method, what sound will it make?

The solution

The fact of the matter is that the BabyBasset can make both sounds; it can howl as well as yelp. I don’t know whether this is true in real life, but for the example presented above, the BabyBasset class has two Bark() methods, one derived from the Hound class and the other derived from the Puppy class. So, the problem really isn’t what sound it makes; rather, it’s how do you make it bark at all? Look at the following code.

BabyBasset bbObj;

bbObj.Bark();    //Will not compile.

The code above will not compile. The compiler will flag an error message Request for member ‘Bark’ is ambiguous. What does that mean? It simply means that the compiler doesn’t know which method to invoke, the Hound’s ‘howl’ or the Puppy’s ‘yelp’. This problem can be solved by fully qualifying the method that you want to invoke. Use the scope resolution operator (::) to specify which Bark() function is to be called, like this.

BabyBasset bbObj;

bbObj.Hound::Bark();    //Will howl.
bbObj.Puppy::Bark();    //Will yelp.

The scope resolution operator is used in C++ to resolve a number of ambiguous situations. And, don’t forget to make the Hound and Puppy classes virtual, so that the Diamond Problem doesn’t crop up.

More by Author

Must Read