Chapter 22 - Other Topics Outline 22.1 Introduction 22.2 const_cast Operator 22.3 reinterpret_cast Operator 22.4 namespaces 22.5 Operator Keywords 22.6 explicit Constructors 22.7 mutable Class Members 22.8 Pointers to Class Members (.* and ->*) 22.9 Multiple Inheritance 22.10 Multiple Inheritance and virtual Base Classes
Consider additional C++ features 22.1 Introduction Consider additional C++ features Cast operators Namespaces Operator keywords Multiple inheritence
22.2 const_cast Operator const_cast operator Used to cast away const or volatile Get rid of a variable's "const-ness" const_cast < new data type >
Function is const, and cannot modify data. 1 // Fig. 22.1: fig22_01.cpp 2 // Demonstrating operator const_cast. 3 #include <iostream> 4 5 using std::cout; 6 using std::endl; 7 8 // class ConstCastTest definition 9 class ConstCastTest { 10 public: 11 void setNumber( int ); 12 int getNumber() const; 13 void printNumber() const; 14 private: 15 int number; 16 }; // end class ConstCastTest 17 18 // set number 19 void ConstCastTest::setNumber( int num ) { number = num; } 20 21 // return number 22 int ConstCastTest::getNumber() const { return number; } 23 fig22_01.cpp (1 of 2) Function is const, and cannot modify data.
fig22_01.cpp (2 of 2) fig22_01.cpp output (1 of 1) 24 // output number 25 void ConstCastTest::printNumber() const 26 { 27 cout << "\nNumber after modification: "; 28 29 // cast away const-ness to allow modification 30 const_cast< ConstCastTest * >( this )->number--; 31 32 cout << number << endl; 33 34 } // end printNumber 35 36 int main() 37 { 38 ConstCastTest test; // create ConstCastTest instance 39 40 test.setNumber( 8 ); // set private data number to 8 41 42 cout << "Initial value of number: " << test.getNumber(); 43 44 test.printNumber(); 45 return 0; 46 47 } // end main Cast away the const-ness the this pointer. This allows the data to be modified. fig22_01.cpp (2 of 2) fig22_01.cpp output (1 of 1) Initial value of number: 8 Number after modification: 7
22.3 reinterpret_cast Operator Used for nonstandard casts (i.e., one pointer to another) int * to char * Cannot be used for standard casts (i.e, double to int)
fig22_02.cpp (1 of 1) fig22_02.cpp output (1 of 1) 1 // Fig. 22.2: fig22_02.cpp 2 // Demonstrating operator reinterpret_cast. 3 #include <iostream> 4 5 using std::cout; 6 using std::endl; 7 8 int main() 9 { 10 int x = 120; 11 int *ptr = &x; 12 13 // use reinterpret_cast to cast from int * to char * 14 cout << *reinterpret_cast< char * >( ptr ) << endl; 15 16 return 0; 17 18 } // end main fig22_02.cpp (1 of 1) fig22_02.cpp output (1 of 1) Create an int *. Cast it to a char * for printing. 120 is the ASCII value of 'x'. x
Program has identifiers in different scopes Namespace defines scope 22.4 namespaces Program has identifiers in different scopes Sometimes scopes overlap, lead to problems Namespace defines scope Place identifiers and variables within namespace Access with namespace_name::member Note guaranteed to be unique namespace Name { contents } Unnamed namespaces are global Need no qualification Namespaces can be nested
22.4 namespaces using statement using namespace namespace_name; Members of that namespace can be used without preceding namespace_name:: Can also be used with individual member Examples using namespace std Discouraged by some programmers, because includes entire contents of std using namespace std::cout Can write cout instead of std::cout
Note the nested namespace Inner. 1 // Fig. 22.3: fig22_03.cpp 2 // Demonstrating namespaces. 3 #include <iostream> 4 5 using namespace std; // use std namespace 6 7 int integer1 = 98; // global variable 8 9 // create namespace Example 10 namespace Example { 11 12 // declare two constants and one variable 13 const double PI = 3.14159; 14 const double E = 2.71828; 15 int integer1 = 8; 16 17 void printValues(); // prototype 18 19 // nested namespace 20 namespace Inner { 21 22 // define enumeration 23 enum Years { FISCAL1 = 1990, FISCAL2, FISCAL3 }; 24 25 } // end Inner 26 27 } // end Example Note the using statement. This includes all of std, allowing us to use cout and endl. fig22_03.cpp (1 of 3) Create a new namespace, Example. Note that it has a variable integer1, different from the global integer1. Note the nested namespace Inner.
Create an unnamed namespace. Its variables are global. 28 29 // create unnamed namespace 30 namespace { 31 double doubleInUnnamed = 88.22; // declare variable 32 33 } // end unnamed namespace 34 35 int main() 36 { 37 // output value doubleInUnnamed of unnamed namespace 38 cout << "doubleInUnnamed = " << doubleInUnnamed; 39 40 // output global variable 41 cout << "\n(global) integer1 = " << integer1; 42 43 // output values of Example namespace 44 cout << "\nPI = " << Example::PI << "\nE = " 45 << Example::E << "\ninteger1 = " 46 << Example::integer1 << "\nFISCAL3 = " 47 << Example::Inner::FISCAL3 << endl; 48 49 Example::printValues(); // invoke printValues function 50 51 return 0; 52 53 } // end main Create an unnamed namespace. Its variables are global. fig22_03.cpp (2 of 3)
fig22_03.cpp (3 of 3) 54 55 // display variable and constant values 56 void Example::printValues() 57 { 58 cout << "\nIn printValues:\ninteger1 = " 59 << integer1 << "\nPI = " << PI << "\nE = " 60 << E << "\ndoubleInUnnamed = " << doubleInUnnamed 61 << "\n(global) integer1 = " << ::integer1 62 << "\nFISCAL3 = " << Inner::FISCAL3 << endl; 63 64 } // end printValues fig22_03.cpp (3 of 3)
fig22_03.cpp output (1 of 1) doubleInUnnamed = 88.22 (global) integer1 = 98 PI = 3.14159 E = 2.71828 integer1 = 8 FISCAL3 = 1992 In printValues: fig22_03.cpp output (1 of 1)
22.5 Operator Keywords Operator keywords Can be used instead of operators Useful for keyboards without ^ | & etc.
22.5 Operator Keywords
Note use of operator keywords. 1 // Fig. 22.5: fig22_05.cpp 2 // Demonstrating operator keywords. 3 #include <iostream> 4 5 using std::cout; 6 using std::endl; 7 using std::boolalpha; 8 9 #include <iso646.h> 10 11 int main() 12 { 13 int a = 2; 14 int b = 3; 15 16 cout << boolalpha 17 << " a and b: " << ( a and b ) 18 << "\n a or b: " << ( a or b ) 19 << "\n not a: " << ( not a ) 20 << "\na not_eq b: " << ( a not_eq b ) 21 << "\na bitand b: " << ( a bitand b ) 22 << "\na bit_or b: " << ( a bitor b ) 23 << "\n a xor b: " << ( a xor b ) 24 << "\n compl a: " << ( compl a ) 25 << "\na and_eq b: " << ( a and_eq b ) 26 << "\n a or_eq b: " << ( a or_eq b ) 27 << "\na xor_eq b: " << ( a xor_eq b ) << endl; fig22_05.cpp (1 of 2) Note use of operator keywords.
fig22_05.cpp (2 of 2) fig22_05.cpp output (1 of 1) 28 29 return 0; 30 31 } // end main fig22_05.cpp (2 of 2) fig22_05.cpp output (1 of 1) a and b: true a or b: true not a: false a not_eq b: false a bitand b: 3 a bit_or b: 3 a xor b: 0 compl a: -4 a and_eq b: 3 a or_eq b: 3 a xor_eq b: 1
22.6 explicit Constructors Implicit conversions In Chapter 8, compiler may perform implicitly If constructor exists Suppose we have constructor myClass( int x ) Define function myFunction( myClass y ) Now, call myFunction( 3 ) Compiler implicitly converts 3 to myClass, using the constructor Then calls myFunction with the new object
22.6 explicit Constructors May not have desired behavior Declare constructor explicit Cannot be used in implicit conversions Example First show Array class with implicit conversion Then show Array class with explicit constructor
1 // Fig 22.6: array.h 2 // Simple class Array (for integers). 3 #ifndef ARRAY_H 4 #define ARRAY_H 5 6 #include <iostream> 7 8 using std::ostream; 9 10 // class Array definition 11 class Array { 12 friend ostream &operator<<( ostream &, const Array & ); 13 public: 14 Array( int = 10 ); // default/conversion constructor 15 ~Array(); // destructor 16 private: 17 int size; // size of the array 18 int *ptr; // pointer to first element of array 19 20 }; // end class Array 21 22 #endif // ARRAY_H array.h (1 of 1) Without the explicit keyword, this constructor can be used for implicit conversions.
array.cpp (1 of 2) 1 // Fig 22.7: array.cpp 2 // Member function definitions for class Array. 3 #include <iostream> 4 5 using std::cout; 6 using std::ostream; 7 8 #include <new> 9 10 #include "array.h" 11 12 // default constructor for class Array (default size 10) 13 Array::Array( int arraySize ) 14 { 15 size = ( arraySize < 0 ? 10 : arraySize ); 16 cout << "Array constructor called for " 17 << size << " elements\n"; 18 19 // create space for array 20 ptr = new int[ size ]; 21 22 // initialize array elements to zeroes 23 for ( int i = 0; i < size; i++ ) 24 ptr[ i ] = 0; 25 26 } // end constructor array.cpp (1 of 2)
array.cpp (2 of 2) 27 28 // destructor for class Array 29 Array::~Array() { delete [] ptr; } 30 31 // overloaded stream insertion operator for class Array 32 ostream &operator<<( ostream &output, const Array &arrayRef ) 33 { 34 for ( int i = 0; i < arrayRef.size; i++ ) 35 output << arrayRef.ptr[ i ] << ' ' ; 36 37 return output; // enables cout << x << y; 38 39 } // end operator<< array.cpp (2 of 2)
1 // Fig 22.8: fig22_08.cpp 2 // Driver for simple class Array. 3 #include <iostream> 4 5 using std::cout; 6 7 #include "array.h" 8 9 void outputArray( const Array & ); 10 11 int main() 12 { 13 Array integers1( 7 ); 14 15 outputArray( integers1 ); // output Array integers1 16 17 outputArray( 15 ); // convert 15 to an Array and output 18 19 return 0; 20 21 } // end main fig22_08.cpp (1 of 2) Call outputArray and pass an int. This works because the int is implicitly converted to an Array by the constructor.
fig22_08.cpp (2 of 2) fig22_08.cpp output (1 of 1) 23 // print array contents 24 void outputArray( const Array &arrayToOutput ) 25 { 26 cout << "The array received contains:\n" 27 << arrayToOutput << "\n\n"; 28 29 } // end outputArray fig22_08.cpp (2 of 2) fig22_08.cpp output (1 of 1) Array constructor called for 7 elements The array received contains: 0 0 0 0 0 0 0 Array constructor called for 15 elements 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
This time, declare constructor explicit. 1 // Fig. 22.9: array.h 2 // Simple class Array (for integers). 3 #ifndef ARRAY_H 4 #define ARRAY_H 5 6 #include <iostream> 7 8 using std::ostream; 9 10 // class Array definition 11 class Array { 12 friend ostream &operator<<( ostream &, const Array & ); 13 public: 14 explicit Array( int = 10 ); // default constructor 15 ~Array(); // destructor 16 private: 17 int size; // size of the array 18 int *ptr; // pointer to first element of array 19 20 }; // end class Array 21 22 #endif // ARRAY_H array.h (1 of 1) This time, declare constructor explicit.
array.cpp (1 of 2) 1 // Fig. 22.10: array.cpp 2 // Member function definitions for class Array. 3 #include <iostream> 4 5 using std::cout; 6 using std::ostream; 7 8 #include <new> 9 10 #include "array.h" 11 12 // default constructor for class Array (default size 10) 13 Array::Array( int arraySize ) 14 { 15 size = ( arraySize < 0 ? 10 : arraySize ); 16 cout << "Array constructor called for " 17 << size << " elements\n"; 18 19 // create space for array 20 ptr = new int[ size ]; 21 22 // initialize array elements to zeroes 23 for ( int i = 0; i < size; i++ ) 24 ptr[ i ] = 0; 25 26 } // end constructor array.cpp (1 of 2)
array.cpp (2 of 2) 27 28 // destructor for class Array 29 Array::~Array() { delete [] ptr; } 30 31 // overloaded insertion operator for class Array 32 ostream &operator<<( ostream &output, const Array &arrayRef ) 33 { 34 for ( int i = 0; i < arrayRef.size; i++ ) 35 output << arrayRef.ptr[ i ] << ' ' ; 36 37 return output; // enables cout << x << y; 38 39 } // end operator<< array.cpp (2 of 2)
This call will cause an error when compiled. 1 // Fig. 22.11: fig22_11.cpp 2 // Driver for simple class Array. 3 #include <iostream> 4 5 using std::cout; 6 7 #include "array.h" 8 9 void outputArray( const Array & ); 10 11 int main() 12 { 13 Array integers1( 7 ); 14 15 outputArray( integers1 ); // output Array integers1 16 17 // ERROR: construction not allowed 18 outputArray( 15 ); // convert 15 to an Array and output 19 20 outputArray( Array( 15 ) ); // must use constructor 21 22 return 0; 23 24 } // end main 25 fig22_11.cpp (1 of 2) This call will cause an error when compiled.
fig22_11.cpp (2 of 2) fig22_11.cpp output (1 of 1) 26 // display array contents 27 void outputArray( const Array &arrayToOutput ) 28 { 29 cout << "The array received contains:\n" 30 << arrayToOutput << "\n\n"; 31 32 } // end outputArray fig22_11.cpp (2 of 2) fig22_11.cpp output (1 of 1) c:\cpp4e\ch22\FIG22_09_10_11\Fig22_11.cpp(18) : error C2664: 'outputArray' : cannot convert parameter 1 from 'const int' to 'const class Array &' Reason: cannot convert from 'const int' to 'const class Array' No constructor could take the source type, or constructor overload resolution was ambiguous Error executing cl.exe. test.exe - 1 error(s), 0 warning(s)
22.7 mutable Class Members mutable data member const_cast vs. mutable Always modifiable, even in a const function or object Avoid need for const_cast const_cast vs. mutable For const object with no mutable data members const_cast used every time Reduces chance of accidental change
Declare a mutable int. It can be modified by const functions. 1 // Fig. 21.12: fig21_12.cpp 2 // Demonstrating storage class specifier mutable. 3 #include <iostream> 4 5 using std::cout; 6 using std::endl; 7 8 // class TestMutable definition 9 class TestMutable { 10 public: 11 TestMutable( int v = 0 ) { value = v; } 12 void modifyValue() const { value++; } 13 int getValue() const { return value; } 14 private: 15 mutable int value; // mutable member 16 17 }; // end class TestMutable 18 19 int main() 20 { 21 const TestMutable test( 99 ); 22 23 cout << "Initial value: " << test.getValue(); 24 25 test.modifyValue(); // modifies mutable member 26 cout << "\nModified value: " << test.getValue() << endl; fig21_12.cpp (1 of 2) Declare a mutable int. It can be modified by const functions.
fig21_12.cpp (2 of 2) fig21_12.cpp output (1 of 1) 27 28 return 0; 29 30 } // end main fig21_12.cpp (2 of 2) fig21_12.cpp output (1 of 1) Initial value: 99 Modified value: 100
22.8 Pointers to Class Members (.* and ->*) Use to access class members Not the same as previously discussed pointers Class function void *memPtr () Regular function pointer, to function that returns void and takes no arguments void ( Test::*memPtr )() Pointer to function in class Test Function returns void, takes no arguments To call function Need pointer to Test object (tPtr) (tPtr->*memPtr)()
22.8 Pointers to Class Members (.* and ->*) Class data member int *vPtr Regular pointer to an int int Test::*vPtr Pointer to an int member of class Test To access Need pointer to object (tPtr) (*tPtr).*vPtr
fig22_13.cpp (1 of 2) 1 // Fig. 22.13 : fig22_13.cpp 2 // Demonstrating operators .* and ->*. 3 #include <iostream> 4 5 using std::cout; 6 using std::endl; 7 8 // class Test definition 9 class Test { 10 public: 11 void function() { cout << "function\n"; } 12 int value; // public data member 13 }; // end class Test 14 15 void arrowStar( Test * ); 16 void dotStar( Test * ); 17 18 int main() 19 { 20 Test test; 21 22 test.value = 8; // assign value 8 23 arrowStar( &test ); // pass address to arrowStar 24 dotStar( &test ); // pass address to dotStar fig22_13.cpp (1 of 2)
Next, call function directly. 25 26 return 0; 27 28 } // end main 29 30 // access member function of Test object using ->* 31 void arrowStar( Test *testPtr ) 32 { 33 // declare function pointer 34 void ( Test::*memPtr )() = &Test::function; 35 36 // invoke function indirectly 37 ( testPtr->*memPtr )(); 38 39 } // end arrowStar 40 41 // access members of Test object data member using .* 42 void dotStar( Test *testPtr2 ) 43 { 44 int Test::*vPtr = &Test::value; // declare pointer 45 46 cout << ( *testPtr2 ).*vPtr << endl; // access value 47 48 } // end dotStar fig22_13.cpp (2 of 2) Assign function pointer to the address of function in Test. Note that neither side refers to a specific object. Next, call function directly. Create pointer to data member value. Then, access the data.
function 8 fig22_13.cpp output (1 of 1)
22.9 Multiple Inheritance Multiple inheritence Derived class has several base classes Powerful, but can cause ambiguity problems If both base classes have functions of the same name Solution: specify exact function using :: myObject.BaseClass1::function() Format Use comma-separated list class Derived : public Base1, public Base2{ contents }
This base class contains an int. 1 // Fig. 22.14: base1.h 2 // Definition of class Base1 3 #ifndef BASE1_H 4 #define BASE1_H 5 6 // class Base1 definition 7 class Base1 { 8 public: 9 Base1( int parameterValue ) { value = parameterValue; } 10 int getData() const { return value; } 11 12 protected: // accessible to derived classes 13 int value; // inherited by derived class 14 15 }; // end class Base1 16 17 #endif // BASE1_H base1.h (1 of 1) There are two base classes in this example, each has its own getData function. This base class contains an int.
base2.h (1 of 1) 1 // Fig. 22.15: base2.h 2 // Definition of class Base2 3 #ifndef BASE2_H 4 #define BASE2_H 5 6 // class Base2 definition 7 class Base2 { 8 public: 9 Base2( char characterData ) { letter = characterData; } 10 char getData() const { return letter; } 11 12 protected: // accessible to derived classes 13 char letter; // inherited by derived class 14 15 }; // end class Base2 16 17 #endif // BASE2_H base2.h (1 of 1)
Use comma-separated list. 1 // Fig. 22.16: derived.h 2 // Definition of class Derived which inherits 3 // multiple base classes (Base1 and Base2). 4 #ifndef DERIVED_H 5 #define DERIVED_H 6 7 #include <iostream> 8 9 using std::ostream; 10 11 #include "base1.h" 12 #include "base2.h" 13 14 // class Derived definition 15 class Derived : public Base1, public Base2 { 16 friend ostream &operator<<( ostream &, const Derived & ); 17 18 public: 19 Derived( int, char, double ); 20 double getReal() const; 21 22 private: 23 double real; // derived class's private data 24 25 }; // end class Derived 26 27 #endif // DERIVED_H derived.h (1 of 1) Use comma-separated list.
Note use of base-class constructors in derived class constructor. 1 // Fig. 22.17: derived.cpp 2 // Member function definitions for class Derived 3 #include "derived.h" 4 5 // constructor for Derived calls constructors for 6 // class Base1 and class Base2. 7 // use member initializers to call base-class constructors 8 Derived::Derived( int integer, char character, double double1 ) 9 : Base1( integer ), Base2( character ), real( double1 ) { } 10 11 // return real 12 double Derived::getReal() const { return real; } 13 14 // display all data members of Derived 15 ostream &operator<<( ostream &output, const Derived &derived ) 16 { 17 output << " Integer: " << derived.value 18 << "\n Character: " << derived.letter 19 << "\nReal number: " << derived.real; 20 21 return output; // enables cascaded calls 22 23 } // end operator<< Note use of base-class constructors in derived class constructor. derived.cpp (1 of 1)
fig22_18.cpp (1 of 2) 1 // Fig. 22.18: fig22_18.cpp 2 // Driver for multiple inheritance example. 3 #include <iostream> 4 5 using std::cout; 6 using std::endl; 7 8 #include "base1.h" 9 #include "base2.h" 10 #include "derived.h" 11 12 int main() 13 { 14 Base1 base1( 10 ), *base1Ptr = 0; // create Base1 object 15 Base2 base2( 'Z' ), *base2Ptr = 0; // create Base2 object 16 Derived derived( 7, 'A', 3.5 ); // create Derived object 17 18 // print data members of base-class objects 19 cout << "Object base1 contains integer " 20 << base1.getData() 21 << "\nObject base2 contains character " 22 << base2.getData() 23 << "\nObject derived contains:\n" << derived << "\n\n"; 24 fig22_18.cpp (1 of 2)
Note calls to specific base class functions. 25 // print data members of derived-class object 26 // scope resolution operator resolves getData ambiguity 27 cout << "Data members of Derived can be" 28 << " accessed individually:" 29 << "\n Integer: " << derived.Base1::getData() 30 << "\n Character: " << derived.Base2::getData() 31 << "\nReal number: " << derived.getReal() << "\n\n"; 32 33 cout << "Derived can be treated as an " 34 << "object of either base class:\n"; 35 36 // treat Derived as a Base1 object 37 base1Ptr = &derived; 38 cout << "base1Ptr->getData() yields " 39 << base1Ptr->getData() << '\n'; 40 41 // treat Derived as a Base2 object 42 base2Ptr = &derived; 43 cout << "base2Ptr->getData() yields " 44 << base2Ptr->getData() << endl; 45 46 return 0; 47 48 } // end main fig22_18.cpp (2 of 2) Note calls to specific base class functions. Can treat derived-class pointer as either base-class pointer.
fig22_18.cpp output (1 of 1) Object base1 contains integer 10 Object base2 contains character Z Object derived contains: Integer: 7 Character: A Real number: 3.5 Data members of Derived can be accessed individually: Derived can be treated as an object of either base class: base1Ptr->getData() yields 7 base2Ptr->getData() yields A fig22_18.cpp output (1 of 1)
22.10 Multiple Inheritance and virtual Base Classes Ambiguities from multiple inheritance iostream could have duplicate subobjects Data from ios inherited into ostream and istream Upcasting iostream pointer to ios object is a problem Two ios subobjects could exist, which is used? Ambiguous, results in syntax error iostream does not actually have this problem ios ostream istream iostream
22.10 Multiple Inheritance and virtual Base Classes Solution: use virtual base class inheritance Only one subobject inherited into multiply derived class Second Derived Class Base Class First Derived Class Multiply-Derived Class virtual inheritance
This example will demonstrate the ambiguity of multiple inheritance. 1 // Fig. 22.20: fig22_20.cpp 2 // Attempting to polymorphically call a function that is 3 // multiply inherited from two base classes. 4 #include <iostream> 5 6 using std::cout; 7 using std::endl; 8 9 // class Base definition 10 class Base { 11 public: 12 virtual void print() const = 0; // pure virtual 13 14 }; // end class Base 15 16 // class DerivedOne definition 17 class DerivedOne : public Base { 18 public: 19 20 // override print function 21 void print() const { cout << "DerivedOne\n"; } 22 23 }; // end class DerivedOne 24 fig22_20.cpp (1 of 3) This example will demonstrate the ambiguity of multiple inheritance.
fig22_20.cpp (2 of 3) 25 // class DerivedTwo definition 26 class DerivedTwo : public Base { 27 public: 28 29 // override print function 30 void print() const { cout << "DerivedTwo\n"; } 31 32 }; // end class DerivedTwo 33 34 // class Multiple definition 35 class Multiple : public DerivedOne, public DerivedTwo { 36 public: 37 38 // qualify which version of function print 39 void print() const { DerivedTwo::print(); } 40 41 }; // end class Multiple 42 fig22_20.cpp (2 of 3)
Which base subobject will be used? 43 int main() 44 { 45 Multiple both; // instantiate Multiple object 46 DerivedOne one; // instantiate DerivedOne object 47 DerivedTwo two; // instantiate DerivedTwo object 48 49 // create array of base-class pointers 50 Base *array[ 3 ]; 51 52 array[ 0 ] = &both; // ERROR--ambiguous 53 array[ 1 ] = &one; 54 array[ 2 ] = &two; 55 56 // polymorphically invoke print 57 for ( int i = 0; i < 3; i++ ) 58 array[ i ] -> print(); 59 60 return 0; 61 62 } // end main fig22_20.cpp (3 of 3) Which base subobject will be used?
c:\cpp4e\ch22\fig22_20_21\fig22_20 c:\cpp4e\ch22\fig22_20_21\fig22_20.cpp(52) : error C2594: '=' : ambiguous conversions from 'class Multiple *' to 'class Base *' Error executing cl.exe. test.exe - 1 error(s), 0 warning(s) fig22_20.cpp output (1 of 1)
Use virtual inheritance to solve the ambiguity problem. 1 // Fig. 22.21: fig22_21.cpp 2 // Using virtual base classes. 3 #include <iostream> 4 5 using std::cout; 6 using std::endl; 7 8 // class Base definition 9 class Base { 10 public: 11 12 // implicit default constructor 13 14 virtual void print() const = 0; // pure virtual 15 16 }; // end Base class 17 18 // class DerivedOne definition 19 class DerivedOne : virtual public Base { 20 public: 21 22 // implicit default constructor calls 23 // Base default constructor 24 25 // override print function 26 void print() const { cout << "DerivedOne\n"; } 27 28 }; // end DerivedOne class fig22_21.cpp (1 of 3) Use virtual inheritance to solve the ambiguity problem. The compiler generates default constructors, which greatly simplifies the hierarchy.
Use virtual inheritance, as before. fig22_21.cpp (2 of 3) 29 30 // class DerivedTwo definition 31 class DerivedTwo : virtual public Base { 32 public: 33 34 // implicit default constructor calls 35 // Base default constructor 36 37 // override print function 38 void print() const { cout << "DerivedTwo\n"; } 39 40 }; // end DerivedTwo class 41 42 // class Multiple definition 43 class Multiple : public DerivedOne, public DerivedTwo { 44 public: 45 46 // implicit default constructor calls 47 // DerivedOne and DerivedTwo default constructors 48 49 // qualify which version of function print 50 void print() const { DerivedTwo::print(); } 51 52 }; // end Multiple class Use virtual inheritance, as before. fig22_21.cpp (2 of 3)
fig22_21.cpp (3 of 3) 53 54 int main() 55 { 55 { 56 Multiple both; // instantiate Multiple object 57 DerivedOne one; // instantiate DerivedOne object 58 DerivedTwo two; // instantiate DerivedTwo object 59 60 // declare array of base-class pointers and initialize 61 // each element to a derived-class type 62 Base *array[ 3 ]; 63 64 array[ 0 ] = &both; 65 array[ 1 ] = &one; 66 array[ 2 ] = &two; 67 68 // polymorphically invoke function print 69 for ( int i = 0; i < 3; i++ ) 70 array[ i ]->print(); 71 72 return 0; 73 74 } // end main fig22_21.cpp (3 of 3)
DerivedTwo DerivedOne fig22_21.cpp output (1 of 1)