int z;
protected:
void specialPrint(ostream& os) const {
// Only print Right's part
os << ','<< z;
}
public:
Right(int m, int n) : Top(m) { z = n; }
friend ostream&
operator<<(ostream& os, const Right& r) {
return os << static_cast<const Top&>(r) << ',' << r.z;
}
};
class Bottom : public Left, public Right {
int w;
public:
Bottom(int i, int j, int k, int m)
: Top(i), Left(0, j), Right(0, k) { w = m; }
friend ostream&
operator<<(ostream& os, const Bottom& b) {
os << static_cast<const Top&>(b);
b.Left::specialPrint(os);
b.Right::specialPrint(os);
return os << ',' << b.w;
}
};
int main() {
Bottom b(1, 2, 3, 4);
cout << b << endl; // 1,2,3,4
} ///:~
The specialPrint( ) functions are protected since they will be called only by Bottom. They print only their own data and ignore their Top subobject, because the Bottom inserter is in control when these functions are called. The Bottom inserter must know about the virtual base, just as a Bottom constructor needs to. This same reasoning applies to assignment operators in a hierarchy with a virtual base, as well as to any function, member or not, that wants to share the work throughout all classes in the hierarchy.
Having discussed virtual base classes, we can now illustrate the "full story" of object initialization. Since virtual bases give rise to shared subobjects, it makes sense that they should be available before the sharing takes place. Therefore, the order of initialization of subobjects follows these rules (recursively, as needed, of course):
3.All virtual base class subobjects are initialized, in top-down, left-to-right order according to where they appear in class definitions.
4.Non-virtual base classes are then initialized in the usual order.
5.All member objects are initialized in declaration order.
6.The complete object’s constructor executes.
The following program illustrates this behavior.
//: C09:VirtInit.cpp
// Illustrates initialization order with virtual bases
#include <iostream>
#include <string>
using namespace std;
class M {
public:
M(const string& s) {
cout << "M " << s << endl;
}
};
class A{
M m;
public:
A(const string& s) : m("in A") {
cout << "A " << s << endl;
}
};
class B
{
M m;
public:
B(const string& s) : m("in B") {
cout << "B " << s << endl;
}
};
class C
{
M m;
public:
C(const string& s) : m("in C") {
cout << "C " << s << endl;
}
};
class D
{
M m;
public:
D(const string& s) : m("in D") {
cout << "D " << s << endl;
}
};
class E : public A, virtual public B, virtual public C
{
M m;
public:
E(const string& s)
: A("from E"), B("from E"), C("from E"), m("in E") {
cout << "E " << s << endl;
}
};
class F : virtual public B, virtual public C, public D
{
M m;
public:
F(const string& s)
: B("from F"), C("from F"), D("from F"), m("in F") {
cout << "F " << s << endl;
}
};
class G : public E, public F
{
M m;
public:
G(const string& s)
: B("from G"), C("from G"), E("from G"),
F("from G"), m("in G") {
cout << "G " << s << endl;
}
};
int main() {
G g("from main");
} ///:~
The classes in this code can be represented by the following diagram:
Each class has an embedded member of type M. Note that only four derivations are virtuaclass="underline" E from B and C, and F from B and C. The output of this program is
M in B
B from G
M in C
C from G
M in A
A from E
M in E
E from G
M in D
D from F
M in F
F from G
M in G
G from main
The initialization of g requires its E and F part to first be initialized, but the B and C subobjects are initialized first, because they are virtual bases, and are initialized from G’s initializer, G being the most-derived class. The class B has no base classes, so according to rule 3, its member object m is initialized, then its constructor prints "B from G", and similarly for the C subject of E. The E subobject requires A, B, and C subobjects. Since B and C have already been initialized, the A subobject of the E subobject is initialized next, and then the E subobject itself. The same scenario repeats for g’s F subobject, but without duplicating the initialization of the virtual bases.
Name lookup issues
The ambiguities we have illustrated with subobjects apply, of course, to any names, including function names. If a class has multiple direct base classes that share member functions of the same name, and you call one of those member functions, the compiler doesn’t know which one to choose. The following sample program would report such an error.
// C09:AmbiguousName.cpp
class Top {};
class Left : virtual public Top {
public:
void f(){}
};
class Right : virtual public Top {
public:
void f(){}
};
class Bottom : public Left, public Right {};
int main() {
Bottom b;
b.f(); // error here
}
The class Bottom has inherited two functions of the same name (the signature is irrelevant, since name lookup occurs before overload resolution), and there is no way to choose between them. The usual technique to disambiguate the call is to qualify the function call with the base class name: