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.

MI Is not Mission Impossible

3.) The Siamese Twins problem

This is a more subtle type of problem. There's no compile or runtime error. The problem has more to do with a very specific situation. Here goes:

//Resource manipulation class.
class CResource
{
   public:
      CResource()
      {
         errorCode = 20;
      }

      //Virtual function that needs to be overriden.
      virtual int CleanUp(void) = 0;

   private:
      int errorCode;
};


//Data manipulation class.
class CData
{
   public:
      CData()
      {
         errorCode = 10;
      }

      //Virtual function that needs to be overriden.
      virtual int CleanUp(void) = 0;

   private:
      int errorCode;
};



//Resource and Data Controlling class.
//Derives from the CResource and CData classes.
class CRDController : public CResource, public CData
{
   private:
      int errorCode;

   public:
      int CleanUp (void)
      {
         //Clean up of Resources / Data
      }
};

CResource and CData are two base classes that contain purely virtual functions named CleanUp(). These functions are meant to be overridden by the classes that derive from them. The problem comes when both these classes are inherited by a single 'CRDController' class. This class needs to override the virtual CleanUp() functions of both CResource and CData separately; in other words, the CleanUp() of CResource does one thing (perhaps, the cleaning up of resources) whereas the CleanUp() method of CData does something else entirely. However, CRDController can only override the CleanUp() functions with one CleanUp() function. So, the above code will happily compile, but the end result is not what you want.

The solution

You can't change the names of the CleanUp() methods in the base classes because these classes might be part of a library, the source code of which you don't really have. Here's the trick; this might not seem pretty, but it works. First, make two intermediate classes, each of which derives from CResource and CData, like this:

//Intermediate classes.
class CResourceInt : public virtual CResource
{
   protected:
      CResourceInt()
      {}

      virtual int CleanUpResource(void) = 0;
      int CleanUp(void)    //Override for Base class method.
      {
         return CleanUpResource();
      }
};


class CDataInt : public virtual CData
{
   protected:
      CDataInt()
      {}

      virtual int CleanUpData(void) = 0;
      int CleanUp(void)    //Override for Base Class method.
      {
         return CleanUpData();
      }
};

Now, each intermediate class overrides the CleanUp() method of its base class individually, thereby clearly distinguishing one CleanUp() from the other. Now, what you do is make the CRDController class derive from both intermediate classes and override their virtual methods (CleanUpResource() and CleanUpData()) In this way, you indirectly override the CleanUp() methods of CResource and CData. Here's what the new CRDController declaration should look like.

//Resource and Data Controlling class.
//Derives from the intermediate classes.
class CRDController : public CResourceInt, public CDataInt
{
   private:
      int errorCode;

   public:
      //Override intermediate class function.
      //Indirectly overrides Base class function.
      int CleanUpResource(void)
      {
         printf("\nCleanUp of Resources complete...");
         return 1;
      }


      //Override intermediate class function.
      //Indirectly overrides Base class function.
      int CleanUpData(void)
      {
         printf("\nCleanUp of Data complete...");
         return 1;
      }
};

The Siamese twins problem is not a very common scenario that you are likely to come across. In any case, it's better to be prepared in the event that you do run into such a problem. A word of caution, though: Make certain that no class further derives from the CRDController class; otherwise, the whole thing might blow up! You might want to seal up the CRDController class as well as the intermediate classes. (For all you C# programmers out there, I know that there's no actual 'sealed' keyword in unmanaged C++, but there is another nifty trick that you could employ to achieve this effect. The explanation of that technique is for another time and place.)

Concluding this article on MI, I would like to mention that many programmers shy away from this powerful mechanism that is built into the C++ language. Other languages, such as C#, have redesigned the way you use it. In C#, you can derive from multiple interfaces (an interface is something like a pure abstract class having no data members and only pure virtual functions that must be overridden). This, of course, gets rid of most of the stumbling blocks in conventional multiple inheritance. Also, you can explicitly override a method in a particular interface. So, this takes care of the Siamese twins problem. Remember, multiple inheritance is not dead. It's just gone through a phase; but if you want to, you can still use it in its conventional form.

References:

  1. Herb Sutter, 'Uses and Abuses of Inheritance' - Part II, C++ Report, Oct. 1998.


About the Author

Angelo Rohit

Angelo Rohit is a student pursuing his Master's in Computer Applications in India. Apart from C++ and C# coding, his interests include reading Sherlock Holmes novels and demolishing NightElf bases in Warcraft III. Recent obsessions involve the Win32 API, Cryptography, jogging (for obvious reasons) and targeting Orcs (they're no longer cute).

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • The explosion in mobile devices and applications has generated a great deal of interest in APIs. Today's businesses are under increased pressure to make it easy to build apps, supply tools to help developers work more quickly, and deploy operational analytics so they can track users, developers, application performance, and more. Apigee Edge provides comprehensive API delivery tools and both operational and business-level analytics in an integrated platform. It is available as on-premise software or through …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds