operator<<(ostream& os, const Bottom& b) {
return os << static_cast
<< ',' << static_cast
<< ',' << b.w;
}
};
int main() {
Bottom b(1, 2, 3, 4);
cout << b << endl; // 1,2,1,3,4
} ///:~
You can’t just blindly share the responsibility upward in the usual fashion because the Left and Right stream inserters each call the Top inserter, and again there will be duplication of data. Instead you need to mimic what the compiler automatically does with initialization. One solution is to provide special functions in the classes that know about the virtual base class, which ignore the virtual base when printing (leaving the job to the most derived class):
//: C09:VirtualBase3.cpp
// A correct stream inserter
#include
using namespace std;
class Top {
int x;
public:
Top(int n) { x = n; }
friend ostream&
operator<<(ostream& os, const Top& t) {
return os << t.x;
}
};
class Left : virtual public Top {
int y;
protected:
void specialPrint(ostream& os) const {
// Only print Left's part
os << ','<< y;
}
public:
Left(int m, int n) : Top(m) { y = n; }
friend ostream&
operator<<(ostream& os, const Left& l) {
return os << static_cast
}
};
class Right : virtual public Top {
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
}
};
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
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
#include
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 virtual: 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