16.5 VIRTUAL BASES AND ASSIGNMENT OPERATORS


16.5 VIRTUAL BASES AND ASSIGNMENT OPERATORS

Given that a virtual base requires modifying the syntax of the copy constructors for the downstream classes in a class hierarchy, what about the assignment operator definitions for the downstream classes? As it turns out, the syntax of the assignment operator function remains the same whether or not a base is virtual.

With that good news out of the way, we will use the rest of this section to give the reader a visual proof of the presence of duplicate base-class subobjects inside a derived-class object if the common-base is not declared virtual. Using the X, Y, Z, T, U hierarchy of Figure 16.8, our goal is to demonstrate visually that if X is not a virtual base, an object of type U will have sitting inside it two subobjects of type X. This we will do by playing with the assignment-operator overload definition for class U. Our demonstration will consist of showing to the reader two programs, one a correct implementation of the hierarchy of Figure 16.8 and another that is deliberately incorrect. Here is a top-level view of what is in the rest of this section:

  1. We will first show the program VirtualBaseCopyConstruct.cc of the previous section with the assignment operator implementations added. The new program will be called VirtualBaseAssign.cc.

  2. Next, in order to make a graphic demonstration of the presence of duplicate subobjects when a common base is not virtual, we will drop the "virtual" declaration of the common base in the program VirtualBaseAssign.cc and make appropriate changes to the rest of the program to make it consistent with a nonvirtual base. This new program will be called DuplicateBase.cc.

  3. Finally, we will do something deliberately subversive in the program DuplicateBase.cc. In the assignment-operator definition for U, we will suppress the assignments along the right inheritance path in the hierarchy of Figure 16.8. So, when we assign a U object u2 to another U object u1, the latter will retain its own subobjects that correspond to the right inheritance path in the hierarchy. So if the X subobjects inside u1 and u2 are different, we should see them both in the new u1 after the assignment.

Here is the program of the previous section with the assignment operator included for each of the classes.

 
//VirtualBaseAssign.cc #include <iostream> using namespace std; class X { int x; public: X(int xx) : x(xx) {} X(const X& other) : x(other.x) {} //assignment op: X& operator=(const X& other) { if (this == &other) return *this; x = other.x; return *this; } virtual void print() { cout << "printing value of x of X subobject: " << x << endl; } }; class Y : virtual public X { int y; public: Y(int xx, int yy) : X(xx), y(yy) {} Y(const Y& other) : X(other), y(other.y) {} //assignment op: Y& operator=(const Y& other) { if (this == &other) return *this; X::operator=(other); y = other.y; return *this; } void print() { X::print(); cout << "printing value of y of Y subobject: " << y << endl; } }; class T : virtual public X { int t; public: T(int xx, int tt) : X(xx), t(tt) {} T(const T& other) : X(other), t(other.t) {} // assignment op: T& operator=(const T& other) { if (this == *other) return *this; X::operator=(other); t = other.t; return *this; } void print() { X::print(); cout << "printing value of t of T subobject: " << t << endl; } }; class Z : public Y { int z; public: Z( int xx, int yy, int zz) : Y(xx, yy), X(xx), z(zz) {} Z( const Z& other) : Y(other), X(other), z(other.z) {} // assignment op: Z& operator=(const Z& other) { if (this == &other) return *this; Y::operator=(other); z = other.z; return *this; } void print () { Y::print(); cout << "printing value of z of Z subobject: " << z << endl; } }; class U : public Z, public T { int u; public: U ( int xx, int yy, int zz, int tt, int uu) : Z(xx, yy, zz), T(xx, tt), X(xx), u(uu) {} U ( const U& other) : Z(other), T(other), X(other), u(other.u) {} // assignment op: U& operator=(const U& other) { if (this == &other) return *this; Z::operator=(other); //(A) T::operator=(other); //(B) u = other.u; return *this; } void print() { Z::print(); T::print(); cout << "printing value of u of U subobject: " << u << endl; } }; int main() { cout << "U object coming up: " << endl; U u_obj_1(9100, 9200, 9300, 9400, 9500); u_obj_1.print(); //(C) cout << endl; U u_obj_2(7100, 7200, 7300, 7400, 7500); u_obj_2 = u_obj_1; //(D) cout << "U object after assignment: " << endl; u_obj_2.print(); //(E) return 0; }

The above program produces the following output:

 U object coming up: printing value of x of X subobject: 9100    //output of line (C) printing value of y of Y subobject: 9200 printing value of z of Z subobject: 9300 printing value of x of X subobject: 9100 printing value of t of T subobject: 9400 printing value of u of U subobject: 9500 The U object after assignment to another U object: printing value of x of X subobject: 9100     //output of line (E) printing value of y of Y subobject: 9200 printing value of z of Z subobject: 9300 printing value of x of X subobject: 9100 printing value of t of T subobject: 9400 printing value of u of U subobject: 9500 

The program shown above, VirtualBaseAssign.cc, is a correct implementation of the class hierarchy of Figure 16.8. Shown below is a deliberately incorrect implementation of this hierarchy-we declare in lines (F) and (G) the common-base X to be nonvirtual and make other changes to the previous program to make it consistent with a nonvirtual X. The commented out lines indicate the changes that were made to VirtualBaseAssign.cc to obtain the new program. The changes made to the constructor and copy constructor for class Z are shown in lines (H) and (I), and those made to the the constructor and the copy constructor for class (U) are shown in lines (J) and (K).

 
//DuplicateBase.cc #include <iostream.h> using namespace std; class X { int x; public: X(int xx) : x(xx) {} X(const X& other) : x(other.x) {} X& operator=(const X& other) { if (this == &other) return *this; x = other.x; return *this; } virtual void print() { cout << "printing value of x of X subobject: " << x << endl; } }; //class Y : virtual public X { class Y : public X { // base is now nonvirtual //(F) int y; public: Y(int xx, int yy) : X(xx), y(yy) {} Y(const Y& other) : X(other), y(other.y) {} Y& operator=(const Y& other) { if (this == &other) return *this; X::operator=(other); y = other.y; return *this; } void print() { X::print(); cout << "printing value of y of Y subobject: " << y << endl; } }; //class T : virtual public X { class T : public X { // base is now nonvirtual //(G) int t; public: T(int xx, int tt) : X(xx), t(tt) {} T(const T& other) : X(other), t(other.t) {} T& operator=(const T& other) { if (this == &other) return *this; X::operator=(other); t = other.t; return *this; } void print() { X::print(); cout << "printing value of t of T subobject: " << t << endl; } }; class Z : public Y { int z; public: // Z(int xx, int yy, int zz) : Y(xx, yy), X(xx), z(zz) {} Z(int xx, int yy, int zz) * Y(xx, yy), z(zz) {} //(H) // Z(const Z& other) : Y(other), X(other), z(other.z) {} Z(const Z& other) : Y(other), z(other.z) {} //(I) Z& operator=(const Z& other) { if (this == &other) return *this; Y::operator=(other); z = other.z; return *this; } void print() { Y:: print(); cout << "printing value of z of Z subobject: " << z << endl; } }; class U : public Z, public T { int u; public: U (int xx, int yy, int zz, int tt, int uu) // : Z(xx, yy, zz), T(xx, tt), X(xx), u(uu) {} : Z(xx, yy, zz), T(xx, tt), u(uu) {} //(J) U(const U& other) // :Z(other), T(other), X(other), u(other.u) {} : Z(other), T(other), u(other.u) {} //(K) U& operator=(const U& other) { if (this == &other) return *this; Z::operator=(other); //(L) T::operator=(other); //(M) u = other.u; } return *this; } void print() { Z::print(); T::print(); cout << "printing value of u of U subobject: " << u << endl; } }; int main() { cout << "U object coming up: " << endl; U u_obj_1(9100, 9200, 9300, 9400, 9500); u_obj_1.print(); //(N) cout << endl; U u_obj_2(7100, 7200, 7300, 7400, 7500); u_obj_2 = u_obj_1; cout << "The U object after assignment from another U object: " << endl; u_obj_2.print(); //(O) cout << endl; return 0; }

The program produces the following output:

 U object coming up: printing value of x of X subobject: 9100     // output of line (N) printing value of y of Y subobject: 9200 printing value of z of Z subobject: 9300 printing value of x of X subobject: 9100 printing value of t of T subobject: 9400 printing value of u of U subobject: 9500 The U object after assignment from another U object: printing value of x of X subobject: 9100     // output of line (O) printing value of y of Y subobject: 9200 printing value of z of Z subobject: 9300 printing value of x of X subobject: 9100 printing value of t of T subobject: 9400 printing value of u of U subobject: 9500 

Now if we comment out line (M) in U's assignment operator function and run the same program again, we get the following output:

 U object coming up: printing value of x of X subobject: 9100     // output of line (N) printing value of y of Y subobject: 9200 printing value of z of Z subobject: 9300 printing value of x of X subobject: 9100 printing value of t of T subobject: 9400 printing value of u of U subobject: 9500 The U object after assignment from another U object: printing value of x of X subobject: 9100     // output of line (O)  //(P) printing value of y of Y subobject: 9200 printing value of z of Z subobject: 9300 printing value of x of X subobject: 7100                            //(Q) printing value of t of T subobject: 7400 printing value of u of U subobject: 9500 

Note that in the copied over U object, the two X subobjects have two different values. The X subobject corresponding to the left inheritance path in the class diagram of Figure 16.8 has a value of 9100, as shown at (P), and the X subobject corresponding to the right inheritance path has the a value of 7100, as shown at (Q). This constitutes a direct visual proof of the existence of multiple subobjects of the common base type when we do not use virtual bases.




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net