Chapter 21 - Standard C++ Language Additions Outline 21.1 21.2 21.3 21.4 21.5 21.6 21.7 21.8 21.9 21.10 21.11 21.12 Introduction bool Data Type static_cast Operator const_cast Operator reinterpret_cast Operator namespaces Run-Time Type Information (RTTI) Operator Keywords explicit Constructors mutable Class Members Pointers to Class Members (.* and ->*) Multiple Inheritance and virtual Base Classes 2000 Deitel & Associates, Inc. All rights reserved. 21.1 Introduction • We shall cover standard C++ features – data type bool – cast operators – namespaces – run-time type information (RTTI) – operator keywords 2000 Deitel & Associates, Inc. All rights reserved. 21.2 bool Data Type • bool - can be false or true – preferable to 0 (false) and non-zero (true) • outputting bool variables – numerical default (0 or 1) – stream manipulator boolalpha • outputs string "true" or "false" Examples: cout << boolVariable cout << boolalpha << boolVariable 2000 Deitel & Associates, Inc. All rights reserved. 1 // Fig. 21.1: fig21_01.cpp 2 // Demonstrating data type bool. 3 #include <iostream> Outline 1. Initialize bool variable an int. 4 5 using std::cout; 6 using std::endl; 7 using std::cin; 8 using std::boolalpha; 2. Input a variable. 3. Print the bool value of the int. 9 10 int main() 3.1 Print the value of bool variable. 11 { 12 bool boolean = false; 13 int x = 0; 14 15 16 17 cout << "boolean is " << boolean << "\nEnter an integer: "; boolean is 0 Enter an integer: 22 cin >> x; 18 19 cout << "integer " << x << " is" 20 << ( x ? " nonzero " : " zero " ) 21 << "and interpreted as "; 22 2000 Deitel & Associates, Inc. All rights reserved. 23 24 25 26 27 28 29 30 31 32 33 34 } if ( x ) cout << "true\n"; integer 22 is nonzero and interpreted as true else cout << "false\n"; boolean cout << cout << << = true; "boolean is " << boolean; "\nboolean output with boolalpha manipulator is " boolalpha << boolean << endl; return 0; Outline 3.2 Print the string value of the bool variable. boolean is 1 boolean output with boolalpha manipulator is Notice how the true boolean is 0 Enter an integer: 22 integer 22 is nonzero and interpreted as true boolean is 1 boolean output with boolalpha manipulator is true 2000 Deitel & Associates, Inc. All rights reserved. output varies. Program Output 21.3 static_cast Operator • C++ has 4 separate, specific casts • static_cast - conversion between types – type checking at compile time – standard conversions: void* to char*, int to float, etc. – base class pointers to derived class pointers • Format: static_cast<type to convert to>(object to convert) int z = 3; float x = static_cast<int>(z); 2000 Deitel & Associates, Inc. All rights reserved. 1 // Fig. 21.2: fig21_02.cpp 2 // Demonstrating the static_cast operator. 3 #include <iostream> 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Outline 1. Define class BaseClass using std::cout; using std::endl; class BaseClass { public: void f( void ) const { cout << "BASE\n"; } 1.1 Define member function }; 1.2 Define class DerivedClass class DerivedClass : public BaseClass { public: void f( void ) const { cout << "DERIVED\n"; } }; 18 void test( BaseClass * ); 19 20 int main() convert 21 { 22 // use static_cast for a conversion 23 double d = 8.22; 1.3 Define member function double to int 24 int x = static_cast< int >( d ); 25 26 cout << "d is " << d << "\nx is " << x << endl; 27 28 BaseClass * basePtr = new DerivedClass; 29 test( basePtr ); // call test 2000 delete Deitel & Associates, Inc. All rights reserved. 30 basePtr; 1.4 Global function prototype (calls function f) 2. Function calls d is 8.22 x is 8 3. Output results 31 32 Outline return 0; 33 } 3.1 Function definition 34 35 void test( BaseClass * basePtr ) 36 { 37 DerivedClass *derivedPtr; 38 39 // cast base class pointer into derived class pointer 40 derivedPtr = static_cast< DerivedClass * >( basePtr ); 41 derivedPtr->f(); // invoke DerivedClass function f 42 } d is 8.22 x is 8 DERIVED Program Output converts a base pointer to a derived pointer, and calls the derived function f 2000 Deitel & Associates, Inc. All rights reserved. 1 // Fig. 21.2: fig21_02.cpp 2 // Demonstrating the static_cast operator. 3 #include <iostream> 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using std::cout; using std::endl; In const function print the this pointer is originally const ConstCastTest *. It is cast into type ConstCastTest *, and can then be modified. class BaseClass { public: void f( void ) const { cout << "BASE\n"; } }; class DerivedClass : public BaseClass { public: void f( void ) const { cout << "DERIVED\n"; } }; 18 void test( BaseClass * ); 19 20 int main() 21 { 22 // use static_cast for a conversion 23 double d = 8.22; 24 int x = static_cast< int >( d ); 25 26 cout << "d is " << d << "\nx is " << x << endl; 27 28 BaseClass * basePtr = new DerivedClass; 29 test( basePtr ); // call test 2000 delete Deitel & Associates, Inc. All rights reserved. 30 basePtr; Outline 1. Class definition 1.1 Initialize objects 2. Print 31 32 Outline return 0; 33 } 34 35 void test( BaseClass * basePtr ) 36 { 37 3. Function definition DerivedClass *derivedPtr; 38 39 // cast base class pointer into derived class pointer 40 derivedPtr = static_cast< DerivedClass * >( basePtr ); 41 derivedPtr->f(); // invoke DerivedClass function f 42 } d is 8.22 x is 8 DERIVED 2000 Deitel & Associates, Inc. All rights reserved. Program Output 21.4 const_cast Operator • const_cast - cast away const or volatile – cannot be used directly to cast away const-ness • use pointers 2000 Deitel & Associates, Inc. All rights reserved. 1 // Fig. 21.3: fig21_03.cpp 2 // Demonstrating the const_cast operator. Outline 3 #include <iostream> 4 5 using std::cout; 6 using std::endl; 7 8 class ConstCastTest { 9 public: 10 void setNumber( int ); 11 int getNumber() const; 12 void printNumber() const; 13 private: 14 int number; 15 }; 16 17 void ConstCastTest::setNumber( int num ) { number = num; } 18 19 int ConstCastTest::getNumber() const { return number; } 20 21 void ConstCastTest::printNumber() const 22 { 23 cout << "\nNumber after modification: "; 24 25 the expression number-would generate compile error 2000 // Deitel & Associates, Inc. All rights reserved. 1. Define class ConstCastTest 1.1 Define member functions 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 // undo const-ness to allow modification const_cast< ConstCastTest * >( this )->number--; Outline cout << number << endl; } 2. Create and initialize object Casts the this pointer into type ConstCastTest *. This casts away int main() the "const-ness" and allows number to be modified. { ConstCastTest x; 3. Modify and print x.setNumber( 8 ); // set private data number to 8 object with a const function. cout << "Initial value of number: " << x.getNumber(); x.printNumber(); return 0; } Initial value of number: 8 Number after modification: 7 2000 Deitel & Associates, Inc. All rights reserved. Program Output 21.5 reinterpret_cast Operator • reinterpret_cast - for nonstandard casts – one pointer type to another pointer type, void* to int, etc. – cannot be used for standard casts (int to double, etc.). 2000 Deitel & Associates, Inc. All rights reserved. 1 // Fig. 21.4: fig21_04.cpp 2 // Demonstrating the reinterpret_cast operator. 3 #include <iostream> 1. Initialize variables and pointers 4 5 using std::cout; 6 using std::endl; 7 8 int main() 9 { 10 Outline ptr (type int *) cast to a pointer of type (char *). int x = 120, *ptr = &x; 2. Cast a pointer to pointer of a different type 3. Output data 11 12 cout << *reinterpret_cast<char *>( ptr ) << endl; 13 14 return 0; 15 } Program Output x 120 is the ASCII character code for 'x' 2000 Deitel & Associates, Inc. All rights reserved. 21.6 namespaces • variables with same name and different scopes can overlap – need to distinguish them • a namespace defines a scope for local and global identifiers. – body delimited by braces {} – use (::) to access namespace members: namespace_name::member – or, a using statement must occur before name is used using namespace namespace_name; -members of the namespace do not need a prefix – not guaranteed to be unique – can be nested 2000 Deitel & Associates, Inc. All rights reserved. 21.6 namespaces (II) • Unnamed namespaces – – – – occupy global namespace directly accessible do not need namespace name global variables are in global namespace • accessible in all scopes 2000 Deitel & Associates, Inc. All rights reserved. 1 // Fig. 21.5: fig21_05.cpp 2 // Demonstrating namespaces. 3 #include <iostream> 4 using namespace std; // use std namespace int myInt = 98; // global variable Outline 5 6 1.1 Declare global variable 7 8 1. Use std namespace namespace Example { 9 const double PI = 3.14159; 10 const double E = 2.71828; 11 int myInt = 8; 12 void printValues(); 1.2 Define namespace Example 13 14 namespace Inner { 15 16 // nested namespace enum Years { FISCAL1 = 1990, FISCAL2, FISCAL3 }; } 17 } 18 19 namespace { 20 // unnamed namespace Unnamed namespace members do not need qualifiers 22 23 int main() 24 { 25 // output value d of unnamed namespace 26 cout << "d = " << d; d = 88.22 (global) myInt = 98 // output global variable 2000 cout Deitel & Inc. All rights reserved. 29 <<Associates, "\n(global) myInt = " << myInt; 28 1.4 Define unnamed namespace double d = 88.22; 21 } 27 1.3 Define namespace inner 2. Print variables 31 // output values of Example namespace 32 cout << "\nPI = " << Example::PI << "\nE = " 33 << Example::E << "\nmyInt = " 34 << Example::myInt << "\nFISCAL3 = " 35 << Example::Inner::FISCAL3 << endl; Example::printValues(); 38 39 2. Print variables 3. Function definition PI = 3.14159 36 37 Outline E = 2.71828 // invoke printValues function myInt = 8 In printValues: FISCAL3 myInt = = 8 1992 return 0; PI = 3.14159 E = 2.71828 40 } d = 88.22 41 (global) myInt = 98 42 void Example::printValues() FISCAL3 = 1992 43 { 44 cout << "\nIn printValues:\n" << "myInt = " 45 << myInt << "\nPI = " << PI << "\nE = " 46 << E << "\nd = " << d << "\n(global) myInt = " 47 << ::myInt << "\nFISCAL3 = " 48 << Inner::FISCAL3 << endl; 49 } 2000 Deitel & Associates, Inc. All rights reserved. Function printValues is a member of Example and does not need a namespace qualifier. d = 88.22 (global) myInt = 98 PI = 3.14159 E = 2.71828 myInt = 8 FISCAL3 = 1992 In printValues: myInt = 8 PI = 3.14159 E = 2.71828 d = 88.22 (global) myInt = 98 FISCAL3 = 1992 2000 Deitel & Associates, Inc. All rights reserved. Outline Program Output 21.7 Run-Time Type Information (RTTI) • determines an object's type at run time • typeid (in <typeinfo>) typeid(object).name() - returns the name of the object as a C-style string • dynamic_cast - for polymorphic programming – often used to downcast base-class pointer to derived-class pointer – used with virtual functions 2000 Deitel & Associates, Inc. All rights reserved. 1 // Fig. 21.7: fig21_07.cpp 2 // Demonstrating dynamic_cast. 3 #include <iostream> 4 5 using std::cout; 6 using std::endl; 7 8 const double PI = 3.14159; 9 10 class Shape { 11 public: 12 virtual double area() const { return 0.0; } 13 }; 14 15 class Circle : public Shape { 16 public: 17 Circle( int r = 1 ) { radius = r; } 18 19 virtual double area() const 20 { 21 return PI * radius * radius; 22 }; 23 protected: 24 int radius; 25 }; 26 27 class Cylinder : public Circle { 28 public: 29 Cylinder( int h = 1 ) { height = h; } 30 31 virtual double area() const 32 { 2000 Deitel & Associates, Inc. *Allradius rights reserved. 33 return 2 * PI * height + Outline 1. Define base and derived classes 34 2 * Circle::area(); 35 } 36 private: 37 int height; 38 }; 39 1.1 Function prototype 40 void outputShapeArea( const Shape * ); // prototype 41 42 int main() 1.2 Declare objects 43 { 44 Circle circle; 45 Cylinder cylinder; 2. Function calls 46 Shape *ptr = 0; 47 3. Define function. 48 outputShapeArea( &circle ); // output circle's area 49 outputShapeArea( &cylinder ); // output cylinder's area 50 outputShapeArea( ptr ); // attempt to output area 51 return 0; 52 } 53 54 void outputShapeArea( const Shape *shapePtr ) 55 { Notice how shapePtr is cast to 56 const Circle *circlePtr; various types. If it is not of the 57 const Cylinder *cylinderPtr; right type, the cast returns 0. 58 59 // cast Shape * to a Cylinder * 60 cylinderPtr = dynamic_cast< const Cylinder * >( shapePtr ); 61 62 if ( cylinderPtr != 0 ) // if true, invoke area() 63 cout << "Cylinder's area: " << shapePtr->area(); 64 else { // shapePtr does not refer to a cylinder 65 2000 Deitel Associates, Inc. All rights 66 //& cast shapePtr to a reserved. Circle * Outline 67 circlePtr = dynamic_cast< const Circle * >( shapePtr ); 68 69 if ( circlePtr != 0 ) 70 cout << "Circle's area: " << circlePtr->area(); 71 else 72 73 // if true, invoke area() Notice how shapePtr is cast to Outline various types. If it is not of the right type,3.the cast returns 0. Define function. cout << "Neither a Circle nor a Cylinder."; } 74 75 cout << endl; 76 } Circle's area: 3.14159 Cylinder's area: 12.5664 Neither a Circle nor a Cylinder. 2000 Deitel & Associates, Inc. All rights reserved. Program Output 21.8 Operator Keywords • can use keywords in place of operators (such as !, &, ^, etc.). – use header <iso646.h> (may vary with compiler) Operator Keyword Description Logical operator keywords && and logical AND || or logical OR ! not logical NOT Inequality operator keyword != not_eq inequality Bitwise operator keywords & bitand bitwise AND | bitor bitwise inclusive OR ^ xor bitwise exclusive OR ~ compl bitwise complement Bitwise assignment operator keywords &= and_eq bitwise AND assignment |= or_eq bitwise inclusive OR assignment ^= xor_eq bitwise exclusive OR assignment 2000 Deitel & Associates, Inc. All rights reserved. 1 // Fig. 21.9: fig21_09.cpp 2 // Demonstrating operator keywords. 3 #include <iostream> 4 1. Load header 5 using std::cout; 6 using std::endl; 7 using std::boolalpha; 8 9 Outline 1.1 Initialize variables Operator keywords can be used instead of the symbols #include <iso646.h> 10 11 int main() 2. Use operator keywords 12 { 13 int a = 8, b = 22; 14 15 cout << boolalpha 16 << " a and b: " << ( a and b ) 17 << "\n a or b: " << ( a or b ) 18 << "\n not a: " << ( not a ) 19 << "\na not_eq b: " << ( a not_eq b ) 20 << "\na bitand b: " << ( a bitand b ) 21 << "\na bit_or b: " << ( a bitor b ) 22 << "\n a xor b: " << ( a xor b ) 23 << "\n compl a: " << ( compl a ) 24 << "\na and_eq b: " << ( a and_eq b ) 25 << "\n a or_eq b: " << ( a or_eq b ) 26 << "\na xor_eq b: " << ( a xor_eq b ) << endl; 27 28 return 0; 2000 29 } Deitel & Associates, Inc. All rights reserved. 3. Print results Outline a and b: a or not a not_eq a bitand a bit_or a xor compl a and_eq a or_eq a xor_eq true b: true a: false b: false b: 22 b: 22 b: 0 a: -23 b: 22 b: 30 b: 30 2000 Deitel & Associates, Inc. All rights reserved. Program Output 21.9 explicit Constructors • constructors with one argument can be used for implicit conversion – type received by constructor turned into an object – automatic conversion sometimes undesirable • keyword explicit prevents implicit conversion – use before constructor prototype in class definition 2000 Deitel & Associates, Inc. All rights reserved. 1 // Fig. 21.9: fig21_09.cpp 2 // Demonstrating operator keywords. 3 #include <iostream> Outline 4 5 using std::cout; 6 using std::endl; 7 using std::boolalpha; 8 9 15 is not an Array object, but it is implicitly converted to an Array object using the conversion constructor. This new object is printed using outputArray. #include <iso646.h> 11 int main() 12 { int a = 8, b = 22; 14 15 cout << boolalpha " If the keyword explicit comes before the constructor, this program will issue a compiler error - outputArray cannot take an int. 16 << 17 << "\n a or b: " << ( a or b ) 18 << "\n not a: " << ( not a ) 19 << "\na not_eq b: " << ( a not_eq b ) 20 << "\na bitand b: " << ( a bitand b ) 21 << "\na bit_or b: " << ( a bitor b ) 22 << "\n a xor b: " << ( a xor b ) 23 << "\n compl a: " << ( compl a ) 24 << "\na and_eq b: " << ( a and_eq b ) 25 << "\n a or_eq b: " << ( a or_eq b ) 26 << "\na xor_eq b: " << ( a xor_eq b ) << endl; a and b: " << ( a and b ) 27 28 1.1 Define constructor 1.2 Define destructor 10 13 1. Define Array class return 0; 2000 29 } Deitel & Associates, Inc. All rights reserved. 2. Create object 2.1 Print Array object 2.2 Print int 3. Function definition a and b: a or not a not_eq a bitand a bit_or a xor compl a and_eq a or_eq a xor_eq true b: true a: false b: false b: 22 b: 22 b: 0 a: -23 b: 22 b: 30 b: 30 2000 Deitel & Associates, Inc. All rights reserved. Outline Program Output 1 // Fig 21.10: array2.h 2 // Simple class Array (for integers) Outline 3 #ifndef ARRAY2_H 4 #define ARRAY2_H 5 6 1. Class definition #include <iostream> 7 8 1.1 Function prototype using std::ostream; 9 10 class Array { 11 friend ostream &operator<<( ostream &, const Array & ); 1.2 Member variables 12 public: 13 Array( int = 10 ); // default/conversion constructor 14 ~Array(); // destructor 15 private: 16 int size; // size of the array 17 int *ptr; // pointer to first element of array 18 }; 19 20 #endif 21 // Fig 21.10: array2.cpp 22 // Member function definitions for class Array 23 #include <iostream> 24 25 using std::cout; 26 using std::ostream; 27 28 #include <cassert> 29 #include "array2.h" 2000 Deitel & Associates, Inc. All rights reserved. 30 ---------------------1. Load header file 31 // Default constructor for class Array (default size 10) Outline 32 Array::Array( int arraySize ) 33 { 34 size = ( arraySize > 0 ? arraySize : 10 ); 35 cout << "Array constructor called for " 36 << size << " elements\n"; 37 38 ptr = new int[ size ]; // create space for array 39 assert( ptr != 0 ); // terminate if memory not allocated 40 41 42 for ( int i = 0; i < size; i++ ) ptr[ i ] = 0; // initialize array 43 } 44 45 // Destructor for class Array 46 Array::~Array() { delete [] ptr; } 47 48 // Overloaded output operator for class Array 49 ostream &operator<<( ostream &output, const Array &a ) 50 { 51 int i; 52 53 54 for ( i = 0; i < a.size; i++ ) output << a.ptr[ i ] << ' ' ; 55 56 return output; // enables cout << x << y; 2000 57 } Deitel & Associates, Inc. All rights reserved. 1.1 Function definitions 58 // Fig 21.10: fig21_10.cpp Outline 59 // Driver for simple class Array 60 #include <iostream> 61 1. Load header 62 using std::cout; 63 1.1 Initialize object 64 #include "array2.h" 65 66 void outputArray( const Array & ); 2. Print object 67 3. Function definition 68 int main() 69 { 70 Array integers1( 7 ); 71 72 outputArray( integers1 ); 73 74 outputArray( 15 ); outputArray needs a parameter of type const Array &, so 15 is converted into an Array by the 15 to an Array and output conversion constructor. // output Array integers1 // convert 75 76 return 0; 77 } 78 79 void outputArray( const Array &arrayToOutput ) 80 { 81 82 cout << "The array received contains:\n" << arrayToOutput << "\n\n"; 83 2000 } Deitel & Associates, Inc. All rights reserved. Array constructor called for 7 elements The array received contains: 0 0 0 0 0 0 0 Array constructor called for 15 elements The array received contains: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2000 Deitel & Associates, Inc. All rights reserved. Outline Program Output 21.10 mutable Class Members • mutable data member – always modifiable, even in a const function or object – permanently allows a const data member to be modified • const_cast: – used every time a const data member must be modified – reduces risk of accidentally modifying a const variable 2000 Deitel & Associates, Inc. All rights reserved. 21.11 Pointers to Class Members (.* and ->*) • pointers to class members are different from normal pointers • use .* and ->* instead of . and -> when accessing class members (functions and data) 2000 Deitel & Associates, Inc. All rights reserved. 1 // Fig. 21.13 fig21_13.cpp 2 // Demonstrating operators .* and ->* Outline 3 #include <iostream> 1. Class definition 4 5 using std::cout; 6 using std::endl; 7 8 class Test { 9 public: 10 void function() { cout << "function\n"; } 11 int value; 12 }; 13 14 void arrowStar( Test * ); 15 void dotStar( Test * ); 16 17 int main() 18 { 19 Test t; 20 21 t.value = 8; 22 arrowStar( &t ); 23 dotStar( &t ); 24 return 0; 25 } Deitel & Associates, Inc. All rights reserved. 2000 1.1 Function prototypes 1.2 Initialize object 2. Function calls 26 Outline 27 void arrowStar( Test *tPtr ) 28 { 29 void ( Test::*memPtr )() = &Test::function; 30 ( tPtr->*memPtr )(); 3. Function definitions // invoke function indirectly 31 } arrowStar declares and initializes memPtr to point to a function in Test that takes no parameters and returns no value. 32 33 void dotStar( Test *tPtr ) 34 { 35 int Test::*vPtr = &Test::value; 36 cout << ( *tPtr ).*vPtr << endl; &Test::function to get the offset into //the access class value for member function function. 37 } dotStar declares initializes Without and Test::, memPtr is a standard vPtr to point to value. pointer. function 8 The .* operator is then used to access the member to which vPtr points. 2000 Deitel & Associates, Inc. All rights reserved. Program Output 21.12 Multiple Inheritance and virtual Base Classes • Ambiguities can result with multiple inheritance ios ostream istream iostream • iostream could have duplicate subobjects (data from ios inherited into ostream and istream). – Upcasting an iostream pointer to an ios object creates a problem. Two ios subobjects could exist: which one is used? – Ambiguous, results in syntax error (of course, iostream does not actually have this problem) 2000 Deitel & Associates, Inc. All rights reserved. 21.12 Multiple Inheritance and virtual Base Classes (II) • Use virtual base class inheritance – only one subobject of the base is inherited into the multiply derived class. Base Class virtual inheritance virtual inheritance First Derived Class Second Derived Class Multiply-Derived Class 2000 Deitel & Associates, Inc. All rights reserved. 1 // Fig. 21.15: fig21_15.cpp 2 // Attempting to polymorphically call a function 3 // multiply inherited from two base classes. 4 #include <iostream> 5 6 using std::cout; 7 using std::endl; 8 9 class Base { 10 public: 11 virtual void print() const = 0; // pure virtual 12 }; 13 14 class DerivedOne : public Base { 15 public: 16 // override print function 17 void print() const { cout << "DerivedOne\n"; } 18 }; 19 20 class DerivedTwo : public Base { 21 public: 22 // override print function 23 void print() const { cout << "DerivedTwo\n"; } 24 }; 25 26 class Multiple : public DerivedOne, public DerivedTwo { 27 public: 28 // qualify which version of function print 29 void print() const { DerivedTwo::print(); } 30 }; 2000 Deitel & Associates, Inc. All rights reserved. 31 Outline 1. Define base class 1.1 Define nonvirtual derived classes 1.2 Define multiply derived class 32 int main() 33 { 34 Multiple both; // 35 DerivedOne one; // 36 DerivedTwo two; // 37 38 Base *array[ 3 ]; 39 array[ 0 ] = &both; 40 array[ 1 ] = &one; 41 array[ 2 ] = &two; The address of both is implicitly instantiate Multiple object converted to a base class pointer. This is instantiate DerivedOne ambiguous because object class Multiple has instantiate DerivedTwo duplicate subobjectsobject inherited from Base. 42 // polymorphically invoke print 44 for ( int k = 0; k < 3; k++ ) 45 array[ k ] -> print(); 46 47 return 0; 48 } 2. Initialize array elements If DerivedOne and DerivedTwo had used virtual inheritance, no errors would result and the objects will be printed. Compiling... Fig21_15.cpp fig21_15.cpp(39) : error C2594: '=' : ambiguous conversions from 'class Multiple *' to 'class Base *' 2000 Deitel & Associates, Inc. All rights reserved. 1.3 Create class objects 1.4 Create an array of base class pointers. // ERROR--ambiguous 43 Outline Program Output