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.
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
).
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.
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.
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
.
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:
#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:
#include<iostream>
using namespace std;
class Base
{ public: int i; };
class Der1 : public Base; { public: int j; };
class pre_Der2 { 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;} };
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; 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'.