Click here to Skip to main content
15,895,777 members
Articles / Programming Languages / C++

Manual Virtual Inheritance (MVI)

Rate me:
Please Sign up or sign in to vote.
4.80/5 (6 votes)
3 Sep 2009CPOL3 min read 24.3K   82   13   4
How to enhance the C++ standard library.

Introduction

Virtual inheritance is a powerful and essential tool when it comes to multiple inheritance in C++. The keyword virtual in the inheritance declaration of a class advises the compiler to use it hiding the details from the programmer. Although this is all nice and comfortable, there are cases in which it is necessary to do the job by hand.

Background

Usually, when a child class inherits from a parent class, a complete parent object is embedded in the child object. In the case of virtual inheritance, instead of a whole parent object, there is merely a pointer to a parent object in the child object. This makes it possible for several child objects to access the same parent object, thus allowing things like diamond structure inheritance.

So what is done in MVI?

Well, instead of virtually inheriting from the parent class, a pointer to the parent class type is added to an intermediate pre-child class. Then, all the methods acting upon the parent object are implemented using the parent pointer.

C++
class Parent
{   public: int i;   };

class preChild
{   public: Parent *B; 
    void f(){(*B).i=0;};   };

Now, we have the parent and child code exclusively in two separate classes. Thus, we can easily create a true child class by inheriting from the parent and pre-child classes. To finish it up, we have to set the parent pointer in the pre-child object to the parent object, which is simply setting it to itself (this).

C++
class Child: public Parent, public preChild
{   public: Child(){preChild::B=this;}   };

An application for MVI

MVI is useful when a derived class in the diamond scenario is unchangeable and inherits non-virtually.

                 Base                 iostream
                 /  \                 /      \
                /    \               /        \
Diamond:      Der1  Der2     stringstream  modiostream
                \    /               \        /
                 \  /                 \      /
                 Join              modstringstream

Now, if Der1 inherits from Base non-virtually and can't be changed but Der2 can, then it is still possible to make the diamond work. This is done by the use of MVI. On the right, you can see a case in which this technique can be applied. It's the case in which the C++ standard library is enhanced. Let's say you want to enhance every class of the stream library. So every modified class needs to inherit the class members of the class itself and its base; additionally, it needs to inherit the enhancements made to that base.

Usually, the use of virtual inheritance would solve this problem, but in this case, it does not because stringstream, like most stream classes, inherits from its base iostream non-virtually. This can't be changed because that would mean making changes to the C++ standard library, making it non-standard.

With MVI, the problem is elegantly solvable. For modiostream, an intermediate class (premodiostream) is created that defines the enhancing (mod)-functionality acting on a pointer to an iostream object. modiostream is then created inheriting from iostream and premodiostream. At last, the pointer inherited from premodiostream has to be set on the object itself. modiostream is done.

C++
class premodiostream
{   public: iostream *B;   };

class modiostream : public iostream, public premodiostream
{   public: modiostream(){premodiostream::B=this;}   };

Until now, there is no gain. The gain is achieved when defining modstringstream. modstringstream has to have the functionality of stringstream, of modiostream, and of itself. Thus, modstringstream inherits from stringstream, premodiostream, and can implement its own new methods. Note here that inheriting from stringstream and modiostream (instead of premodiostream) would have caused ambiguities.

C++
class modstringstream : public stringstream, 
                        public premodiostream
{   public: modstringstream(){premodiostream::B=this;}   };

Alternately, instead of implementing its own methods directly, modstringstream could have defined its functionality in a separate class, again like modiostream.

C++
class premodstringstream
{   public: stringstream *Bio; premodiostream *Bss;   };

class modstringstream : public stringstream, 
                        public premodiostream, 
                        public premodstringstream
{   public: modstringstream(){
               premodiostream::B=this;
               premodstringstream::Bio=this;
               premodstringstream::Bss=this; }   };

Examples

Putting the above ideas together, you get the following program:

C++
#include<iostream>
#include<sstream>

using namespace std;

class premodiostream
{   public: iostream *B;   };

class modiostream : public iostream, public premodiostream
{   public: modiostream(){premodiostream::B=this;}   };

class premodstringstream
{   public: stringstream *Bio; premodiostream *Bss;   };

class modstringstream : public stringstream, 
                        public premodiostream, 
                        public premodstringstream
{   public: modstringstream(){
                 premodiostream::B=this;
                 premodstringstream::Bio=this;
                 premodstringstream::Bss=this; }   };

int main(int argc, char** argv)
{
   return 0;
}

Finally, a program demonstrating MVI in another diamond scenario:

C++
#include<iostream>

using namespace std;

class Base
{   public: int i;   };

class Der1 : public Base; // this might be the unchangable class
{   public: int j;   };

class pre_Der2  // the pre-class(es) inherit nothing
{   public: Base *B; 
    void f(){(*B).i=0;};   };

class Der2: public Base, public pre_Der2
{   public: Der2(){B=this;}   };

class Join : public Der1, public pre_Der2
{   public: Join(){B=this;}   };

/* ALTERNATIVELY (if Join needs to inherit using MVI)
*
* class pre_Join
* {   public: Der1 *D1; pre_Der2 *D2;   };
* 
* class Join : public Der1, public pre_Der2, public pre_Join
* {   public: Join(){pre_Der2::B=this; 
*                    pre_Join::D1=this; pre_Join::D2=this;}   };
*/

int main(int argc, char** argv)
{
   Join j;
   cout << "j.i=" << j.i 
        << " j.j=" << j.j << endl;
   j.i=2; j.j=3;
   cout << "j.i=" << j.i 
        << " j.j=" << j.j << endl;
   j.f();
   cout << "j.i=" << j.i 
        << " j.j=" << j.j << endl;

   cout << endl;

   Der2 d;
   cout << "d.i=" << d.i << endl;
   d.i=2; // d.j=3; Der2 has no member named 'j'
   cout << "d.i=" << d.i << endl;
   d.f();
   cout << "d.i=" << d.i << endl;

   return 0;
}

Output:

j.i=-1080947032 j.j=134514096
j.i=2 j.j=3
j.i=0 j.j=3

d.i=134520396
d.i=2
d.i=0

In another project named 'User Interface System', most of the C++ stream classes (ios_base, ios, istream, ostream, iostream, fstream, and stringstream) are enhanced. See the file 'stdUI.hpp'.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionwhere is the multiple inheritance? Pin
Fragiskos7-Sep-09 10:57
Fragiskos7-Sep-09 10:57 
AnswerRe: where is the multiple inheritance? Pin
Jonathan Enders16-Sep-09 8:17
Jonathan Enders16-Sep-09 8:17 
QuestionAm I missing something here ? Pin
Sasa Kajic3-Sep-09 13:00
Sasa Kajic3-Sep-09 13:00 
AnswerRe: Am I missing something here ? Pin
Jonathan Enders16-Sep-09 8:21
Jonathan Enders16-Sep-09 8:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.