virtual
base classes
To
solve the first problem, you must explicitly disambiguate the function
vf( )
by writing a redefinition in the class
mi. The
solution to the second problem is a language extension: The meaning of the
virtual
keyword is overloaded. If you inherit a base class as
virtual,
only one subobject of that class will ever appear as a base class. Virtual base
classes are implemented by the compiler with pointer magic in a way suggesting
the implementation of ordinary virtual functions.
Because
only one subobject of a virtual base class will ever appear during multiple
inheritance, there is no ambiguity during upcasting. Here’s an example:
//: C22:MultipleInheritance2.cpp
// Virtual base classes
#include <iostream>
#include <vector>
#include "../purge.h"
using namespace std;
class MBase {
public:
virtual char* vf() const = 0;
virtual ~MBase() {}
};
class D1 : virtual public MBase {
public:
char* vf() const { return "D1"; }
};
class D2 : virtual public MBase {
public:
char* vf() const { return "D2"; }
};
// MUST explicitly disambiguate vf():
class MI : public D1, public D2 {
public:
char* vf() const { return D1::vf();}
};
int main() {
vector<MBase*> b;
b.push_back(new D1);
b.push_back(new D2);
b.push_back(new MI); // OK
for(int i = 0; i < b.size(); i++)
cout << b[i]->vf() << endl;
purge(b);
The
compiler now accepts the upcast, but notice that you must still explicitly
disambiguate the function
vf( )
in
MI;
otherwise the compiler wouldn’t know which version to use.
The
"most derived" class and virtual base initialization
The
use of virtual base classes isn’t quite as simple as that. The above
example uses the (compiler-synthesized) default constructor. If the virtual
base has a constructor, things become a bit strange. To understand this, you
need a new term:
most-derived
class. The
most-derived class is the one you’re currently in, and is particularly
important when you’re thinking about constructors. In the previous
example,
MBase
is the most-derived class inside the
MBase
constructor. Inside the
D1
constructor,
D1
is the most-derived class, and inside the
MI
constructor,
MI
is the most-derived class.
When
you are using a virtual base class, the most-derived constructor is responsible
for initializing that virtual base class. That means any class, no matter how
far away it is from the virtual base, is responsible for initializing it.
Here’s an example:
//: C22:MultipleInheritance3.cpp
// Virtual base initialization
// Virtual base classes must always be
// Initialized by the "most-derived" class
#include <iostream>
#include <vector>
#include "../purge.h"
using namespace std;
class MBase {
public:
MBase(int) {}
virtual char* vf() const = 0;
virtual ~MBase() {}
};
class D1 : virtual public MBase {
public:
D1() : MBase(1) {}
char* vf() const { return "D1"; }
};
class D2 : virtual public MBase {
public:
D2() : MBase(2) {}
char* vf() const { return "D2"; }
};
class MI : public D1, public D2 {
public:
MI() : MBase(3) {}
char* vf() const {
return D1::vf(); // MUST disambiguate
}
};
class X : public MI {
public:
// You must ALWAYS init the virtual base:
X() : MBase(4) {}
};
int main() {
vector<MBase*> b;
b.push_back(new D1);
b.push_back(new D2);
b.push_back(new MI); // OK
b.push_back(new X);
for(int i = 0; i < b.size(); i++)
cout << b[i]->vf() << endl;
purge(b);
As
you would expect, both
D1
and
D2
must initialize
MBase
in their constructor. But so must
MI
and
X,
even though they are more than one layer away! That’s because each one in
turn becomes the most-derived class. The compiler can’t know whether to
use
D1’s
initialization of
MBase
or to use
D2’s
version. Thus you are always forced to do it in the most-derived class. Note
that only the single selected virtual base constructor is called.
"Tying
off" virtual bases with a default constructor
Forcing
the most-derived class to initialize a virtual base that may be buried deep in
the class hierarchy can seem like a tedious and confusing task to put upon the
client programmer of your class. It’s better to make this invisible,
which is done by creating a default constructor for the virtual base class,
like this:
//: C22:MultipleInheritance4.cpp
// "Tying off" virtual bases
// so you don't have to worry about them
// in derived classes
#include <iostream>
#include <vector>
#include "../purge.h"
using namespace std;
class MBase {
public:
// Default constructor removes responsibility:
MBase(int = 0) {}
virtual char* vf() const = 0;
virtual ~MBase() {}
};
class D1 : virtual public MBase {
public:
D1() : MBase(1) {}
char* vf() const { return "D1"; }
};
class D2 : virtual public MBase {
public:
D2() : MBase(2) {}
char* vf() const { return "D2"; }
};
class MI : public D1, public D2 {
public:
MI() {} // Calls default constructor for MBase
char* vf() const {
return D1::vf(); // MUST disambiguate
}
};
class X : public MI {
public:
X() {} // Calls default constructor for MBase
};
int main() {
vector<MBase*> b;
b.push_back(new D1);
b.push_back(new D2);
b.push_back(new MI); // OK
b.push_back(new X);
for(int i = 0; i < b.size(); i++)
cout << b[i]->vf() << endl;
purge(b);
} ///:~
If
you can always arrange for a virtual base class to have a default constructor,
you’ll make things much easier for anyone who inherits from that class.
Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru