15.19 A C STUDY OF A SMALL CLASS HIERARCHY WITH MODERATELY COMPLEX BEHAVIOR


15.19 A C++ STUDY OF A SMALL CLASS HIERARCHY WITH MODERATELY COMPLEX BEHAVIOR

In Chapter 11 (Section 11.15), we presented a C++ study of interleaved classes of moderate complexity. The purpose of that study was to demonstrate how much care one has to exercise in C++ programming with regard to memory allocation and deallocation, the interaction between classes, inadvertently accessing a null pointer, applying the delete operator where delete [] is actually needed, and so on.

In this section, we will-expand on that example and present a case study that pulls together the following features of C++

  • Run-time type checking.

  • Employing polymorphism in operations such as the sorting of containers of base-class pointers using a criterion that depends on values stored in derived-class objects.

  • Getting around the limitations caused by the fact that polymorphism does not work for operators through the invocation of overridable functions that call the locally defined operators.

These issues will be demonstrated with the help of the two interdependent class hierarchies shown in Figure 15.13. The hierarchies will be interdependent in the sense that Dog has a data member of type Employee and Employee has a data member of type vector<Dog*>, and so on.

click to expand
Figure 15.13

The actual implementation code shown in this section does not include definitions for the derived classes SalesManager and SalesPerson. This was done to keep an already long program from occupying even more space in a book. The reader is, however, urged to program up those extensions as an exercise.

We want the twin class hierarchies shown in Figure 15.13 to exhibit the following properties:

  • Each derived class is to be provided, but only when necessary, with its own copy constructor, copy assignment operator, destructor, overload definition for the output operator, and so on.

  • The Manager class should inherit from Employee a data member dogs declared as:

       vector<Dog*> dogs; 

    While an Employee is allowed to own any ordinary dog, those owned by a Manager should only be Poodles or Chihuahuas. This means that when we assign a container of dogs to a Manager, we must perform runtime type checking to make sure that only Poodles and Chihuahuas are accepted into the dogs data member of the Manager object. The same goes for a SalesManager.

  • A Manager's dogs must always stay sorted by the weight of the dogs. What makes the implementation somewhat complicated is that the weight data member is not defined for the base class Dog, but only for the derived classes Poodle and Chihuahua.

  • The Manager class should possess the following data members for representing an array of Employees:

           Employee* workersSupervised;       int numWorkersSupervised; 
  • The SalesManager class should possess the following data members:

           list<SalesPerson> listOfSalesPersons;       int salesTarget; 

    The data member listOfSalesPersons represents the list of sales Persons supervised by a SalesManager. We will make sure that the list of SalesPerson objects in this data member always stays sorted by the last names. (The implementation of the SalesManager class and incorporation of this feature is left as an exercise for the reader.)

To bring about the functionality listed above, we will first need to make some small changes to the code presented in Section 11.15. For example, in order to store the Poodle and Chihuahua objects in the dogs data member of the parent Employee class, the first change we need to make is to replace the type vector <Dog> by vector <Dog*> for the dogs data member, as shown in line (C) of the header file below. Without this change, we'd only be storing the Dog slices of the Poodle and Chihuahua objects in the dogs data member.

Regarding the matter of keeping sorted by weight the Poodle and the Chihuahua entries in the dogs data member inherited by a Manager, the issue here is how to invoke a sort function on vector <Dog*> if the sorting parameter—weight—exists in only the derived classes of Dog, namely, Poodle and Chihuahua (see lines (K) and (M) of the program Manager. cc that follows the header file Employee. h. We need to somehow scoop up the derived class parameter weight into the base class Dog before invoking sort. To bring about this behavior, in line (A) of the program below we define in the base class Dog the following virtual function

      virtual double getDogCompareParameter() { return 0.0; } 

It has a trivial definition in the base class Dog, but has the following definition in the derived Poodle and Chihuahua classes

      double getDogCompareParameter() { return weight; } 

as shown in lines (L) and (N) of the program Manager. cc shown after the header file Employee. h. Through this mechanism, which depends entirely on polymorphic behavior of the function getDogCompareParameter(), we can "pull" the data member weight from the derived classes into the base class Dog. With that problem solved, we just have to define an appropriate functor for the Dog class for the purpose of sorting:

     class Dog_Comparator {     public:         bool operator() ( Dog* x1, Dog* x2 ) const {             return x1->getDogCompareParameter()                           < x2-<getDogCompareParameter();         }     }; 

Because of polymorphism, this functor will automatically extract the weight of a Dog when the dog is either a Poodle or a Chihuahua object. This functor makes its appearance in line (B) of the header file below.

Another modification that needs to be made to the code of Section 11.15 concerns the overloading of the ‘<<’ operator for the Employee class. Now that we are going to be extending the Dog class, we must ensure that when we output the elements of the vector<Dog*> data member dogs of an Employee, we print out all of the fields of the Poodle and Chihuahua objects. This again requires us to use polymorphism.[16] Since polymorphism does not work on operators, we need to define a print () function for the base class Dog and supply its override definitions for the derived classes Poodle and Chihuahua. In the overload definition for the ‘<<’ operator for Employee, we can then implement the following loop to output the dog information:

     vector<Dog*>::const_iterator p = Employee. dogs. begin();     while ( p != Employee. dogs. end() ) {         (*p++)->print();          cout <<"     ";     } 

Now, through polymorphism, we would be sure that for a Poodle* pointer in the dogs data member the print () for a Poodle would be invoked, and for a Chihuahua* pointer the print() for a Chihuahua would be invoked. For this to work, we obviously need to declare print() in Dog to be virtual. This change is shown in the while loop in line (E) of the header file below.

The other change that needs to be made to the code of Section 11.15 is the inclusion of the following function in the Employee class:

     void addDogToDogs( Dog* newDog ) {         dogs.push_back( newDog );         sort( dogs.begin(), dogs.end(), Dog::Dog_Comparator() );     } 

This function is shown in line (D) of the header file below. Note that a function such as this can be a big trap for extremely difficult to locate bugs in a large C++ program. Since we are pushing pointers into a vector, we must make sure that the pointers point to some freshly acquired memory.

In what follows, first the modified version of the code of Section 11.15 will be shown here.[17] This version, which includes all the changes mentioned above, will be stored in a header file named Employee.h. After that we will show the extensions of the base classes in Employee. h for achieving the behavior mentioned at the beginning of this section. The extended classes will be in a file named Manager.cc to be presented after Employee.h.

 
//Employee.h /********************************************************************** * Modified Version of the code of Section 11.15 of Chapter 11. * The modifications, discussed in this section, are necesary so * that the class hierarchies (to be obtained by extending the * classes in this file) have the specified behavior. * * This file is #included in the file Manager. cc. * * For some detailed and useful comments attached to the various * class and function declarations and definitions shown here, see * the implementation of Employee. cc in Section 11.15 of Chapter 11. ********************************************************************/ #include <string> #include <iostream> #include <vector> #include <map> #include <algorithm> using namespace std; /////////////////// class Auto (declaration only) /////////////////// class Auto; //////////////////////////// class Date ///////////////////////////// class Date { int month; int day; int year; public: Date() : month(0), day(0), year(0) {} Date( int mm, int dd, int yy ) :month( mm ), day( dd ), year( yy ) {} friend ostream& operator<<( ostream& os, const Date& date) { os << "Date of Birth: "<< date.month << "--" << date.day << "--" << date. year; return os; } }; //////////////////////////// class Cat ///////////////////////////// class Cat { string name; int age; public: Cat(){}; Cat( string nam, int a ) : name( nam ), age( a ) {} friend ostream& operator<<( ostream& os, const Cat* cat) { os << "Name: " << cat-;>name << " Age: " << cat->age; return os; } }; //////////////////////////// class Dog ///////////////////////////// class Dog { string name; int age; public: Dog(){} Dog( string nam, int a ) : name( nam ), age( a ) {} friend ostream& operator<<( ostream& os, const Dog dog) { os << "\nName: " << dog.name << " Age: " << dog. age; return os; } virtual void print(){ cout << *this; } virtual double getDogCompareParameter() { return 0.0; } //(A) class Dog_Comparator {— //(B) public: bool operator() ( Dog* x1, Dog* x2) const { return x1->getDogCompareParameter() > x2-<getDogCompareParameter(); } }; virtual ~Dog(){}; friend bool Dog_Comparator::operator() ( Dog* x1, Dog* x2 ) const; }; //////////////////////////// class Employee ///////////////////////// class Employee { string firstName, lastName; Date dateOfBirth; Employee* friends; int numFriends; Auto* autos; int numAutos; Cat* kitty; vector<Dog*> dogs; //(C) map<string, int> phoneList; public: // First constructor: Employee() : friends(0), numFriends(0), autos(0), numAutos(0), kitty(0) {} // Second constructor: Employee( string first, string last ) : firstName( first ), lastName( last ), friends(0), numFriends(0), autos(0), numAutos(0), kitty(0) {} // Third constructor: Employee( string first, string last, Date dob ) : firstName( first ), lastName( last ), dateOfBirth( dob ), friends(0), numFriends(0), autos(0), numAutos(0), kitty(0) {} // Fourth constructor: Employee( string first, string last, Date dob, Cat* kit ) : firstName( first ), lastName( last ), dateOfBirth( dob ), friends(0), numFriends(0), autos(0), numAutos(0) { if ( kit == 0 ) kitty = 0; else kitty = new Cat( *kit ); } // Fifth constructor: Employee( string first, string last, vector<Dog*> dogs ) : firstName( first ), lastName( last ), friends(0), numFriends(0), autos(0), numAutos(0), kitty(0) { vector<Dog*>::iterator iter = dogs.begin(); while ( iter != dogs.end() ) { Dog* p = new Dog( **iter++ ); this->dogs.push_back( p ); } } // Sixth constructor: Employee( string first, string last, map<string, int> phList ) : firstName( first ), lastName( last ), friends(0), numFriends(0), autos(0), numAutos(0), kitty(0) { map<string, int>::iterator iter = phList.begin(); while ( iter != phList.end () ) { phoneList[ iter–>first ] = iter–>second; iter++; } } // Seventh constructor: Employee( string first, string last, Date dob, Employee* fnds, int n ) : firstName( first ), lastName( last ), numFriends( n ), dateOfBirth( dob ), autos(0), numAutos(0), kitty(0) { if( fnds != 0 ) { friends = new Employee[ numFriends ]; for (int i=0; i<numFriends; i++ ) friends [i] = fnds[i]; } } // Eighth constructor: Employee( string first, string last, Date dob, Employee* fnds, int n, Auto* ats, int m, Cat* c ); Employee ( const Employee& other ); Employee& operator=( const Employee& other ); void addDogToDogs ( Dog* newDog ) { //(D) dogs.push_back( newDog ); sort( dogs.begin(), dogs.end(), Dog::Dog_Comparator() ); } virtual ~Employee(); friend ostream& operator<<( ostream& os, const Employee& e ); }; //////////////////////////// class Auto /////////////////////////// class Auto { string autoBrand; Employee owner; public: Auto() {} Auto( string brand ): autoBrand( brand ){} Auto( string brand, Employee e ) : autoBrand( brand ), owner( e ){} friend ostream& operator<<( ostream& os, const Employee& e ); }; ///////////// Remaining definitions for Employee members ///////////// // Eighth constructor: Employee:: Employee( string first, string last, Date dob, Employee* fnds, int n, Auto* ats, int m, Cat* c ) : firstName( first ), lastName( last ), dateOfBirth( dob ), numFriends( n ), numAutos( m ) { if ( fnds == 0 || numFriends == 0 ) friends = 0; else { friends = new Employee[ numFriends ]; for (int i=0; i<numFriends; i ++ ) friends[i] = fnds[i]; } if ( ats == 0 || numAutos == 0) autos = 0; else { autos = new Auto[ numAutos ]; for (int j=0; j<numAutos; j++ ) autos[j] = ats[j]; } if ( c == 0 ) kitty = 0; // special care needed for pointer else kitty = new Cat( *c ); } // Copy constructor: Employee::Employee( const Employee& other ) : firstName( other.firstName ), lastName( other.lastName ), dateOfBirth( other.dateOfBirth ), numFriends( other.numFriends ), numAutos( other.numAutos ), dogs( other.dogs ), // use vector copy constructor phoneList( other.phoneList ) // use map copy constructor { if ( other.friends == 0 || numFriends == 0 ) friends = 0; else { friends = new Employee[ numFriends ]; for (int i=0; i<numFriends; i++ ) friends[i] = other.friends[i]; } if ( other.autos == 0 || numAutos == 0 ) autos = 0; else { autos = new Auto[ numAutos ]; for (int j=0; j<numAutos; J++) autos[j] = other.autos[j]; } if ( other.kitty != 0 ) kitty = new Cat( *other.kitty ); else kitty = 0; } // Copy assignment operator: Employee& Employee::operator=( const Employee& other ) { if ( this == &other ) return *this; firstName = other.firstName; lastName = other.lastName; dateOfBirth = other. dateOfBirth; numFriends = other. numFriends; numAutos = other. numAutos; dogs = other. dogs; // use vector assignment op phoneList = other. phoneList; // use map assignment op if ( other. kitty != 0 ) kitty = new Cat( *other. kitty ); else kitty = 0; if ( friends != 0 ) delete [] friends; if ( other. friends == 0 || numFriends == 0 ) friends = 0; else { friends = new Employee[ numFriends ]; for (int i=0; i<numFriends; i++ ) friends [i] = other.friends[i]; } if ( autos != 0 ) delete [] autos; if ( other. autos == 0 || numAutos == 0 ) autos = 0; else { autos = new Auto[ numAutos ]; for (int j=0; j<numAutos; j++ ) autos[j] = other.autos[j]; } return *this; // Destructor: Employee:: ~Employee() { delete[] friends; delete[] autos; delete kitty; } //overloading of << for Employee class ostream& operator<<( ostream& os, const Employee& e ) { os << e.firstName << " " << e.lastName << endl; os << e.dateOfBirth; os << endl; if ( e.friends != 0 ) { os << "Friends: " ; for (int i=0; i<e.numFriends; i++) os << e.friends[i] .firstName << " " << e.friends[i] .lastName << " "; os << endl; } if ( e.autos != 0 ) { os << "Automobiles: " ; for (int j=0; j<e.numAutos; j++) os << e.autos[j].autoBrand << " " ; os << endl; } os << "Cat info: "; if ( e.kitty == 0 ) os << "the cat died" ; //necessary for no cat else os << e.kitty; os << endl; if ( e.dogs.size() != 0 ) { os << "Dog info:\n"; vector<Dog*>::const_iterator p = e. dogs. begin(); while ( p ! = e.dogs.end() ) { //(E) (*p++)-<print(); cout << " "; } os << endl; } if ( e.phoneList.size() != 0 ) { os << "Phone Nums: "; map<string, int>::const_iterator q = e.phoneList.begin(); while ( q != e.phoneList.end() ) { cout << q->first << ": "<< q->second << " "; q++; } os << endl; } return os; }

We will now show how the classes defined so far can be extended for the desired program behavior described at the beginning of this section. In particular, we mentioned that we wanted to demonstrate runtime type checking for enforcing the condition that the dogs assigned to a Manager be only Poodles and Chihuahuas. The next program, Manager. cc, shows how this is done in Manager's constructor in line (O). The part of the constructor that does runtime type identification (RTTI) to figure out if a Dog* is a PoodleDog* or a ChihuahuaDog* or none of these two is reproduced below:

     vector<Dog*>:: iterator iter = dogs.begin()                  //(F)     while ( iter < dogs.end() ) {         Poodle* p = dynamic_cast<Poodle*>( *iter );      //RTTI  //(G)         if ( p != 0 )             addDogToDogs( new Poodle( *p ) );                    //(H)         Chihuahua* c = dynamic_cast<Chihuahua*>( *iter ); //RTTI //(I)         if ( c != 0 )             addDogToDogs ( new Chihuahua( *c ) );                //(J)         iter++;    } 

where dogs in line (F) is a vector container of type vector<Dog*>. Note how the cast operator dynamic_castDog[18] is used in line (G) to ascertain the true runtime identity of the object pointed to by the iterator. If the pointer returned in line (G) is non-null, then we know for sure that the true identity of the object pointed to by the iterator is Poodle. The same applies in line (I) for testing for a Chihuahua. The function addDogToDogs used in lines (H) and (J) above is defined in line (D) of the header Employee.h. This function is in charge of adding new dogs to the dogs data member that is stored in the Employee slice of a Manager object.

The program behavior specification at the beginning of this section also said that we wanted a Manager's dogs to always stay sorted according to their weights. The implementation of addDogToDogs in line (D) of the header Employee.h guarantees that. Any time we add a new dog to the list of dogs by invoking this function, the function calls on the generic library sort to re–sort the list of dogs. Note how the sort function calls the functor Dog_Comparator defined in line (B) of the Dog class.

Shown below is the source code for Manager.cc. As was mentioned at the very beginning of this section, the code shown here does not include the implementations of the SalesManager and SalesPerson classes. Those are left as exercises for the reader.

 
//Manager.cc #include "Employee.h" using namespace std; //////////////////////////// class Poodle ////////////////////////// class Poodle : public Dog { Employee owner; double weight; //(K) double height; public: Poodle() : owner( Employee() ), weight( 0.0 ), height( 0.0 ) {}; Poodle( Employee owner, string name, int age, double weight, double height ) : Dog( name, age ) { this->owner = owner; this->weight = weight; this->height = height; } friend ostream& operator<<( ostream& os, const Poodle& poodle ) { cout << (Dog) poodle; cout << "\nPedigree: Poodle" << " Weight: " << poodle. weight << " Height: " << poodle. height endl; return os; } void print() { cout << *this; } double getDogCompareParameter() { return weight; } //(L) }; ///////////////////////// class Chihuahua ///////////////////////// class Chihuahua : public Dog { Employee owner; double weight; //(M) double height; public: Chihuahua() : owner( Employee() ), weight( 0.0 ), height( 0.0 ) {}; Chihuahua( Employee owner, string name, int age, double weight, double height ) : Dog( name, age ) { this->owner = owner; this->weight = weight; this->height = height; } friend ostream& operator<<(ostream& os, const Chihuahua& huahua) { cout << (Dog) huahua; cout << "\nPedigree: Chihuahua" << " Weight: " << huahua. weight << " Height: " << huahua.height << endl; return os; } void print() { cout << *this; } double getDogCompareParameter() { return weight; } //(N) }; /////////////////////////// class Manager /////////////////////////// class Manager : public Employee { Employee* workersSupervised; int numWorkersSupervised; public: Manager() : workersSupervised(0), numWorkersSupervised( 0 ) {} Manager( Employee e, vector<Dog*> dogs ) : Employee( e ) { //(0) vector<Dog*>::iterator iter = dogs.begin(); while ( iter < dogs.end() ) { Poodle* p = dynamic_cast<Poodle*>( *iter ); if ( p != 0 ) addDogToDogs( new Poodle( *p ) ); Chihuahua* c = dynamic_cast<Chihuahua*>( *iter ); if ( c != 0 ) addDogToDogs( new Chihuahua( *c ) ); iter++; } } friend ostream& operator<<( ostream& os, const Manager& m ); }; ostream& operator<<( ostream& os, const Manager& m ) { os << (Employee) m; return os; } /////////////////////////////// main ////////////////////////////// int main () { Employee e1( "Zoe", "Zaphod"); // name age Dog dog1( "fido", 3 ); Dog dog2( "spot", 4 ); Dog dog3( "bruno", 2 ); Dog dog4( "darth", 1 ); // Employee name age weight height Poodle dog5( el, "pooch", 4, 15.8, 2.1 ); Poodle dog6( el, "doggy", 3, 12.9, 3.4 ); Poodle dog7( el, "lola", 3, 12.9, 3.4 ); Chihuahua dog8( el, "bitsy", 5, 3.2, 0.3 ); Chihuahua dog9( el, "bookam", 5, 7.2, 0.9 ); Chihuahua dog10( el, "pie", 5, 4.8, 0.7 ); vector<Dog*> dawgs; dawgs.push_back( &dog1 ); dawgs.push_back( &dog2 ); dawgs.push_back( &dog5 ); dawgs.push_back( &dog6 ); dawgs.push_back( &dog3); dawgs.push_back( &dog4 ); dawgs.push_back( &dog10 ); dawgs.push_back( &dog7 ); dawgs.push_back( &dog8 ); dawgs.push_back( &dog9 ); Manager m1( e1, dawgs ); cout << m1; return 0; }

The output of the program is

     Zoe Zaphod     Date of Birth: 0--0--0     Cat info:   the cat died     Dog info:     Name: bitsy   Age: 5     Pedigree: Chihuahua  Weight: 3.2 Height: 0.3     Name: pie   Age: 5     Pedigree: Chihuahua  Weight: 4.8 Height: 0.7     Name: bookam   Age: 5     Pedigree: Chihuahua  Weight: 7.2 Height: 0.9     Name: doggy   Age: 3     Pedigree: Poodle  Weight: 12.9 Height: 3.4     Name: lola   Age: 3     Pedigree: Poodle  Weight: 12.9 Height: 3.4     Name: pooch   Age: 4     Pedigree: Poodle  Weight: 15.8 Height: 2.1 

As the reader can see, the Poodles and the Chihuahuas are stored in sorted order according to their weights.

[16]In the overloading of the ‘<<’ operator for Employee in Section 11.15, we invoked the operator ’<<’ operator directly on the Dog members stored in the dogs data members. But that approach in the current context would only output the Dog slice of a Poodle or a Chihuahua.

[17]For the specific demonstratibns we'll be making in the Manager.cc program shown next in this section, we could have used a much smaller version of this header file. But we have chosen to keep the code of Section 11.15 intact in the header file shown here (except for the modifications mentioned) so that the reader can extend the Manager. cc program along the lines suggested earlier in this section and experiment with the varied data members of the Employee class in its derived classes.

[18]The use of dynamic_ncastDog for runtime type identification (RTTI) is discussed in greater detail in the next chapter.




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