11.15 A C STUDY OF INTERLEAVED CLASSES OF MODERATE COMPLEXITY


11.15 A C++ STUDY OF INTERLEAVED CLASSES OF MODERATE COMPLEXITY

So far in this chapter we have examined separately the many different aspects of C++ and Java classes. It's almost like six blind men trying to identify an elephant by feeling out its different parts. Pulling together this topic-by-topic level of understanding into a more integrated whole is not too difficult for Java, but can be challenging for the case of C++. To alleviate this problem, in this section we will present a mini case study of a moderately complex C++ class. The program shown here is an educational tool to help the reader with the following issues in the design of C++ programs:

  • Class interleaving and how it impacts the design of code with regard to declarations and definitions.

  • Testing for null pointers. A frequent source of bugs in C++ is that a pointer is not tested for null before it is dereferenced.

  • No initialization (or improper initialization) of unused pointer fields in a constructor.

  • When a data member is a pointer that points to an array of objects, not testing for whether the argument pointer in a constructor is non-null before proceeding to allocate memory for the array.

  • Memory leaks caused by the absence of the delete or the delete[] operator in the destructor, or by using just delete where delete[] is needed.

  • When using container classes, not taking advantage of the copy constructors, copy assignment operators, and so on, provided by STL for the container classes.

  • and so on.

We'll consider an Employee class whose data members are of diverse type, each placing a different constraint on how the rest of the code is structured (we are talking about the code involving constructors, copy constructors, copy assignment operators, destructors, and so on):

     class Employee {         string firstName, lastName;         Date dateOfBirth;         Employee* friends;         int numFriends;         Auto* autos;         int numAutos;         Cat* kitty;         vector<Dog> dogs;         map<string, int> phoneList;         // .......     }; 

About the data members of this class and the constraints each places on the design of the program:

  • The first two data members, firstName and lastName, are straightforward, placing no special constraints on the rest of the code.

  • The data member dateOfBirth is of class type. Because dateOfBirth is not a pointer, we'd need to define the Date class before the compiler sees the Employee class.

  • The data member friends is a pointer to an array of Employee objects. Because this data member will point to dynamically allocated memory, we will have to make sure that the destructor of the Employee class invokes the delete[] operator to free up this memory. We will also have to make sure that the code for the constructors, the copy assignment operator, and so on, pays due regard to memory allocation and deallocation. Also, not be to underestimated is the importance of initialization of this pointer data member especially if it is not needed in the body of a constructor. Without appropriate initialization, the destructor could try to delete segments of memory pointed to by the random bits contained in the pointer data member.

  • To keep track of how many friends a given Employee object has, we need the next data member, numFriends.

  • The next data member, autos, is a pointer to an array of Auto objects. Since, as the reader will see shortly, the Auto class has a data member of type Employee, it cannot be defined before the Employee class. So we will assume that when the compiler sees the data member autos, it knows only that Auto is the name of a class. This means that the implementations of Employee constructors, destructors, and so on, that involve autos can only be provided after the class Auto is defined. The comments that were made earlier for the friends data member with regard to memory allocation, deallocation, and initialization, also apply here.

  • The next data member, numAutos, is supposed to keep track of the number of Auto objects in the array autos.

  • The next data member, kitty, is for illustrating that a pointer data member can also be used for an individual object. In this case, we have a choice of defining Cat either before or after the class Employee.

  • The next data member, dogs, is a vector of Dog objects. As we will see, it is much easier to use STL containers for holding groups of objects than to use raw arrays. With STL containers, you can use the copy constructor and the assignment operator supplied by the system. That way all the memory allocation and deallocation headaches related to such data members disappear.

  • The last data member, phoneList, is a map of <string, int> pairs. The comments we made about the dogs data member also apply here.

These constraints are reflected in the source code shown below. The reader should pay particular attention to the comments included. The reader should note especially the layout of the classes. For educational reasons, each definition has been placed the earliest it could be placed without the compiler complaining. For general C++ programming, it is commonly the case that the main class declarations would contain only the function prototypes. We have intentionally deviated from that practice to highlight the ramifications of the fact that a C++ compiler does not possess the look-ahead capability.

Our basic aim in the source code shown below is to be able to create Employee objects with the data members as discussed above. The constructors provided for this class take into the fact that the values for all of the data members may not be known in advance when a new Employee object is created. The source code also defines all the other support classes needed. Each class is provided with

  • one or more constructors,

  • a copy constructor,

  • a copy assignment operator,

  • a destructor,

  • an overload definition for the output stream operator,

but only when necessary. If it is possible for a class to make do with the system supplied defaults for these constructors and operators, we do not gratuitously supply that class with our own definitions.

 
//Interleaved.cc #include <string> #include <iostream> #include <vector> #include <map> using namespace std; /////////////////// class Auto (declaration only) /////////////////// class Auto; // This class is intentionally only declared at this time, but // not defined. It cannot be defined because it needs a // data member of type Employee that the compiler would not // know about at this point. However, we need to declare // Auto as a class because it is needed in Employee. //////////////////////////// class Date //////////////////////////// // The following class is needed to add variety to the // data members of Employee class. The Employee data // member of type Date will be referred to directly, // as opposed to through a pointer, etc. 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; } }; // Note the absence of both a copy constructor and the copy // assignment operator. That's because we'll be content // with the system-supplied default definitions here. // The destructor is missing for the same reason. // Also note that the friend function is defined right // inside the class definition. But this does NOT make // that function a member function of class Date. ///////////////////////////// class Cat ///////////////////////////// // The following class is needed to add a different kind of // variety to the data members of Employee. An object of // type Cat will be referred to via a pointer. This class // does not need a copy constructor or a copy assignment // operator for the same reason as the Date class. 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; } // The way it is written, must check for null pointer for // the second argument in the calling program. Otherwise, // core dump possible. An alternative would consist of // incorporating the null pointer check right here. }; ///////////////////////////// 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 << "Name: " << dog.name << " Age: " << dog.age; return os; } }; ////////////////////////// class Employee /////////////////////////// class Employee { string firstName, lastName; Date dateOfBirth; Employee* friends; int numFriends; Auto* autos; int numAutos; Cat* kitty; vector<Dog> dogs; map<string, int> phoneList; public: // First constructor: Employee() : friends(0), numFriends(0), autos(0), numAutos(0), kitty(0) {} // This is needed for memory allocation statements. If you do not // initialize the pointers as shown, the destructor with its delete // and delete[] operators will try to delete segments of memory that // the random bits point to. And that would lead to program crash. // Initialization of these leads to more foolproof code in the copy // assignment operator for Employee. In general, all variables // should be initialized. So good practice would demand that we // also initialize the other fields. // Second constructor: Employee( string first, string last ) : firstName( first ), lastName( last ), friends(0), numFriends(0), autos(0), numAutos(0), kitty(0) {} // The pointers must be initialized for the same reasons // as for the no-arg constructor // 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 ); //(A) } // The statement at (A) will invoke the copy constructor // of Cat, which in this case will be the system supplied // default copy constructor. A common mistake for // novice programmers is to use the initializer "kitty( kit )" // But that would be asking for trouble if the same Cat // object belonged to more than one Employee objects, since // when the different such Employee objects go out of scope, // they would all be trying to delete the same block of memory, // with possibly disastruous results. // Fifth constructor: // This is for exercising the vector data member. Employee( string first, string last, vector<Dog> dawgs ) : firstName( first ), lastName( last ), dogs( dawgs ), friends(0), numFriends(0), autos(0), numAutos(0), kitty(0) { } // Note how we invoke the system-supplied copy constructor // for the class vector<Dog> to copy from dawgs to the // data member dogs // Sixth constructor: // This is for exercising the map data member. Employee( string first, string last, map<string, int> phList ) : firstName( first ), lastName( last ), phoneList( phList ), friends(0), numFriends(0), autos(0), numAutos(0), kitty(0) {} // Seventh constructor: // In the following constructor, the unused pointers must // be initialized for the same reasons as for the no-arg // 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 ); // this constructor cannot be defined here because the // the definition of the Auto class is not yet complete Employee( const Employee& other ); // this copy constructor cannot be defined here because // the definition of the Auto class is not yet complete Employee& operator=( const Employee& other ); // this copy assignment operator cannot be defined here // because the definition of the Auto class is not yet // complete ~Employee(); // destructor cannot be defined here because the definition // of the Auto class is not yet complete 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 ); }; // Note that the friend declaration above makes no mention of // Auto class. We need this declaration to be able to acces // the data members of Auto in the overload definition of the // output stream operator for Employee. ////// 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: Employeefe 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 ( this->kitty != 0 ) delete kitty; 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() { if ( friends != 0 ) delete[] friends; if ( autos != 0 ) delete[] autos; if ( kitty != 0 ) 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 ran away" ; else os << e.kitty; os << endl; if ( e.dogs.size() != 0 ) { os << "Dog info: "; vector<Dog>::const_iterator p = e.dogs.begin(); while ( p != e.dogs.end() ) cout << *p++ << " "; 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; } /////////////////////////////// main /////////////////////////////// int main() { Employee e1( "Zoe", "Zaphod" ); Employee e2( "YoYo", "Ma", Date( 2, 12, 2000 ) ); Employee empList[2]; empList[0] = e1; // These statements need the empList[1] = e2; // assignment op for Employee Auto autoList[2]; Auto a1( "Chevrolet" ); Auto a2( "Ford" ); autoList[0] = a1; autoList[1] = a2; Cat* purr = new Cat( "socks", 5 ); cout << "TEST 1: " << endl; Employee e3( "Bebe", "Ruth", Dated, 2, 2000), empList, 2, autoList, 2, purr ); Employee e4; e4 = e3; cout << e4; cout << "\n\nTEST 2: " << endl; // what if the kitty pointer is 0 ? Employee e5( "Bebe", "Ruth", Date(1, 2, 2000), empList, 2, autoList, 2, 0); cout << e5; cout << "\n\nTEST 3: " << endl; // what if autoList pointer is 0 ? Employee e6( "Bebe", "Ruth", Date(1, 2, 2000), empList, 2, 0, 0, 0 ); cout << e6; cout << "\n\nTEST 4: " << endl; // what if empList pointer is 0 ? Employee e7( "Bebe", "Ruth", Date(1, 2, 2000), 0, 0, 0, 0, 0 ); cout << e7; Employee e8 = e7; cout << "\n\nTEST 5: " << endl; // try the vector data member Dog dog1( "fido", 3 ); Dog dog2( "spot", 4 ); vector<Dog> dawgs; dawgs.push_back( dog1 ); dawgs.push_back( dog2 ); Employee e9( "Linda", "Ellerbee", dawgs ); cout << e9; cout << "\n\nTEST 6: " << endl; // try the map<string, int> data member map<string, int> phList; phList[ "Steve Martin" ] = 1234567; phList[ "Bill Gates" ] = 100100100; Employee e10( "Will", "Rogers", phList ); cout << e10; cout << "\n\nTEST 7: " << endl; Employee e11 = e10; cout << e11; cout << "\n\nTEST 8: " << endl; e5 = e11; cout << e5; return 0; }

This produces the following output:

 TEST 1: Bebe Ruth Date of Birth: 1--2--2000 Friends: Zoe Zaphod YoYo Ma Automobiles: Chevrolet Ford Cat info: Name: socks Age: 5 TEST 2: Bebe Ruth Date of Birth: 1--2--2000 Friends: Zoe Zaphod YoYo Ma Automobiles: Chevrolet Ford Cat info: the cat ran away TEST 3: Bebe Ruth Date of Birth: 1--2--2000 Friends: Zoe Zaphod YoYo Ma Cat info: the cat ran away TEST 4: Bebe Ruth Date of Birth: 1--2--2000 Cat info: the cat ran away TEST 5: Linda Ellerbee Date of Birth: 0--0--0 Cat info: the cat ran away Dog info: Name: fido Age: 3         Name: spot Age: 4 TEST 6: Will Rogers Date of Birth: 0--0--0 Cat info: the cat ran away Phone Nums: Bill Gates: 100100100   Steve Martin: 1234567 TEST 7: Will Rogers Date of Birth: 0--0--0 Cat info: the cat ran away Phone Nums: Bill Gates: 100100100   Steve Martin: 1234567 TEST 8: Will Rogers Date of Birth: 0--0--0 Cat info: the cat ran away Phone Nums: Bill Gates: 100100100   Steve Martin: 1234567 




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