So far you have worked with single and multiple inheritance to create is-a relationships. Today you will learn
As you have seen in previous examples, it is possible for the member data of a class to include objects of another class. C++ programmers say that the outer class contains the inner class. Thus, an Employee class might contain string objects (for the name of the employee), as well as integers (for the employee's salary and so forth).
Listing 15.1 describes an incomplete, but still useful, String class. This listing does not produce any output. Instead Listing 15.1 will be used with later listings.
Listing 15.1. The String class.
1: #include <iostream.h> 2: #include <string.h> 3: 4: class String 5: { 6: public: 7: // constructors 8: String(); 9: String(const char *const); 10: String(const String &); 11: ~String(); 12: 13: // overloaded operators 14: char & operator[](int offset); 15: char operator[](int offset) const; 16: String operator+(const String&); 17: void operator+=(const String&); 18: String & operator= (const String &); 19: 20: // General accessors 21: int GetLen()const { return itsLen; } 22: const char * GetString() const { return itsString; } 23: // static int ConstructorCount; 24: 25: private: 26: String (int); // private constructor 27: char * itsString; 28: unsigned short itsLen; 29: 30: }; 31: 32: // default constructor creates string of 0 bytes 33: String::String() 34: { 35: itsString = new char[1]; 36: itsString[0] = `\0'; 37: itsLen=0; 38: // cout << "\tDefault string constructor\n"; 39: // ConstructorCount++; 40: } 41: 42: // private (helper) constructor, used only by 43: // class methods for creating a new string of 44: // required size. Null filled. 45: String::String(int len) 46: { 47: itsString = new char[len+1]; 48: for (int i = 0; i<=len; i++) 49: itsString[i] = `\0'; 50: itsLen=len; 51: // cout << "\tString(int) constructor\n"; 52: // ConstructorCount++; 53: } 54: 55: // Converts a character array to a String 56: String::String(const char * const cString) 57: { 58: itsLen = strlen(cString); 59: itsString = new char[itsLen+1]; 60: for (int i = 0; i<itsLen; i++) 61: itsString[i] = cString[i]; 62: itsString[itsLen]='\0'; 63: // cout << "\tString(char*) constructor\n"; 64: // ConstructorCount++; 65: } 66: 67: // copy constructor 68: String::String (const String & rhs) 69: { 70: itsLen=rhs.GetLen(); 71: itsString = new char[itsLen+1]; 72: for (int i = 0; i<itsLen;i++) 73: itsString[i] = rhs[i]; 74: itsString[itsLen] = `\0'; 75: // cout << "\tString(String&) constructor\n"; 76: // ConstructorCount++; 77: } 78: 79: // destructor, frees allocated memory 80: String::~String () 81: { 82: delete [] itsString; 83: itsLen = 0; 84: // cout << "\tString destructor\n"; 85: } 86: 87: // operator equals, frees existing memory 88: // then copies string and size 89: String& String::operator=(const String & rhs) 90: { 91: if (this == &rhs) 92: return *this; 93: delete [] itsString; 94: itsLen=rhs.GetLen(); 95: itsString = new char[itsLen+1]; 96: for (int i = 0; i<itsLen;i++) 97: itsString[i] = rhs[i]; 98: itsString[itsLen] = `\0'; 99: return *this; 100: // cout << "\tString operator=\n"; 101: } 102: 103: //non constant offset operator, returns 104: // reference to character so it can be 105: // changed! 106: char & String::operator[](int offset) 107: { 108: if (offset > itsLen) 109: return itsString[itsLen-1]; 110: else 111: return itsString[offset]; 112: } 113: 114: // constant offset operator for use 115: // on const objects (see copy constructor!) 116: char String::operator[](int offset) const 117: { 118: if (offset > itsLen) 119: return itsString[itsLen-1]; 120: else 121: return itsString[offset]; 122: } 123: 124: // creates a new string by adding current 125: // string to rhs 126: String String::operator+(const String& rhs) 127: { 128: int totalLen = itsLen + rhs.GetLen(); 129: String temp(totalLen); 130: int i, j; 131: for (i = 0; i<itsLen; i++) 132: temp[i] = itsString[i]; 133: for (j = 0; j<rhs.GetLen(); j++, i++) 134: temp[i] = rhs[j]; 135: temp[totalLen]='\0'; 136: return temp; 137: } 138: 139: // changes current string, returns nothing 140: void String::operator+=(const String& rhs) 141: { 142: unsigned short rhsLen = rhs.GetLen(); 143: unsigned short totalLen = itsLen + rhsLen; 144: String temp(totalLen); 145: for (int i = 0; i<itsLen; i++) 146: temp[i] = itsString[i]; 147: for (int j = 0; j<rhs.GetLen(); j++, i++) 148: temp[i] = rhs[i-itsLen]; 149: temp[totalLen]='\0'; 150: *this = temp; 151: } 152: 153: // int String::ConstructorCount = 0;
Output: None.
Analysis: Listing 15.1 provides a String class much like the one used in Listing 11.14 of Day 11, "Arrays." The significant difference here is that the constructors and a few other functions in Listing 11.14 have print statements to show their use, which are currently commented out in Listing 15.1. These functions will be used in later examples.
On line 23, the static member variable ConstructorCount is declared, and on line 153 it is initialized. This variable is incremented in each string constructor. All of this is currently commented out; it will be used in a later listing.
Listing 15.2 describes an Employee class that contains three string objects. Note that a number of statements are commented out; they will be used in later listings.
Listing 15.2. The Employee class and driver program.
1: class Employee 2: { 3: 4: public: 5: Employee(); 6: Employee(char *, char *, char *, long); 7: ~Employee(); 8: Employee(const Employee&); 9: Employee & operator= (const Employee &); 10: 11: const String & GetFirstName() const 12: { return itsFirstName; } 13: const String & GetLastName() const { return itsLastName; } 14: const String & GetAddress() const { return itsAddress; } 15: long GetSalary() const { return itsSalary; } 16: 17: void SetFirstName(const String & fName) 18: { itsFirstName = fName; } 19: void SetLastName(const String & lName) 20: { itsLastName = lName; } 21: void SetAddress(const String & address) 22: { itsAddress = address; } 23: void SetSalary(long salary) { itsSalary = salary; } 24: private: 25: String itsFirstName; 26: String itsLastName; 27: String itsAddress; 28: long itsSalary; 29: }; 30: 31: Employee::Employee(): 32: itsFirstName(""), 33: itsLastName(""), 34: itsAddress(""), 35: itsSalary(0) 36: {} 37: 38: Employee::Employee(char * firstName, char * lastName, 39: char * address, long salary): 40: itsFirstName(firstName), 41: itsLastName(lastName), 42: itsAddress(address), 43: itsSalary(salary) 44: {} 45: 46: Employee::Employee(const Employee & rhs): 47: itsFirstName(rhs.GetFirstName()), 48: itsLastName(rhs.GetLastName()), 49: itsAddress(rhs.GetAddress()), 50: itsSalary(rhs.GetSalary()) 51: {} 52: 53: Employee::~Employee() {} 54: 55: Employee & Employee::operator= (const Employee & rhs) 56: { 57: if (this == &rhs) 58: return *this; 59: 60: itsFirstName = rhs.GetFirstName(); 61: itsLastName = rhs.GetLastName(); 62: itsAddress = rhs.GetAddress(); 63: itsSalary = rhs.GetSalary(); 64: 65: return *this; 66: } 67: 68: int main() 69: { 70: Employee Edie("Jane","Doe","1461 Shore Parkway", 20000); 71: Edie.SetSalary(50000); 72: String LastName("Levine"); 73: Edie.SetLastName(LastName); 74: Edie.SetFirstName("Edythe"); 75: 76: cout << "Name: "; 77: cout << Edie.GetFirstName().GetString(); 78: cout << " " << Edie.GetLastName().GetString(); 79: cout << ".\nAddress: "; 80: cout << Edie.GetAddress().GetString(); 81: cout << ".\nSalary: " ; 82: cout << Edie.GetSalary(); 83: return 0; 84: }
NOTE: Put the code from Listing 15.1 into a file called STRING.HPP. Then any time you need the String class you can include Listing 15.1 by using #include. For example, at the top of Listing 15.2 add the line #include String.hpp. This will add the String class to your program.
Output: Name: Edythe Levine. Address: 1461 Shore Parkway. Salary: 50000
Analysis: Listing 15.2 shows the Employee class, which contains three string objects: itsFirstName, itsLastName, and itsAddress.
On line 70, an Employee object is created, and four values are passed in to initialize the Employee object. On line 71, the Employee access function SetSalary() is called, with the constant value 50000. Note that in a real program this would either be a dynamic value (set at runtime) or a constant.
On line 72, a string is created and initialized using a C++ string constant. This string object is then used as an argument to SetLastName() on line 73.
On line 74, the Employee function SetFirstName() is called with yet another string constant. However, if you are paying close attention, you will notice that Employee does not have a function SetFirstName() that takes a character string as its argument; SetFirstName() requires a constant string reference.
The compiler resolves this because it knows how to make a string from a constant character string. It knows this because you told it how to do so on line 9 of Listing 15.1.
Employee objects do not have special access to the member variables of String. If the Employee object Edie tried to access the member variable itsLen of its own itsFirstName member variable, it would get a compile-time error. This is not much of a burden, however. The accessor functions provide an interface for the String class, and the Employee class need not worry about the implementation details, any more than it worries about how the integer variable, itsSalary, stores its information.
Note that the String class provides the operator+. The designer of the Employee class has blocked access to the operator+ being called on Employee objects by declaring that all the string accessors, such as GetFirstName(), return a constant reference. Because operator+ is not (and can't be) a const function (it changes the object it is called on), attempting to write the following will cause a compile-time error:
String buffer = Edie.GetFirstName() + Edie.GetLastName();
GetFirstName() returns a constant String, and you can't call operator+ on a constant object.
To fix this, overload GetFirstName() to be non-const:
const String & GetFirstName() const { return itsFirstName; } String & GetFirstName() { return itsFirstName; }
Note that the return value is no longer const and that the member function itself is no longer const. Changing the return value is not sufficient to overload the function name; you must change the constancy of the function itself.
It is important to note that the user of an Employee class pays the price of each of those string objects each time one is constructed, or a copy of the Employee is made.
Uncommenting the cout statements in Listing 15.1, lines 38, 51, 63, 75, 84, and 100, reveals how often these are called. Listing 15.3 rewrites the driver program to add print statements indicating where in the program objects are being created:
NOTE: To compile this listing, follow these steps: 1. Uncomment lines 38, 51, 63, 75, 84, and 100 in Listing 15.1. 2. Edit Listing 15.2. Remove lines 64-80 and substitute Listing 15.3. 3. Add #include string.hpp as previously noted.
Listing 15.3. Contained class constructors.
1: int main() 2: { 3: cout << "Creating Edie...\n"; 4: Employee Edie("Jane","Doe","1461 Shore Parkway", 20000); 5: Edie.SetSalary(20000); 6: cout << "Calling SetFirstName with char *...\n"; 7: Edie.SetFirstName("Edythe"); 8: cout << "Creating temporary string LastName...\n"; 9: String LastName("Levine"); 10: Edie.SetLastName(LastName); 11: 12: cout << "Name: "; 13: cout << Edie.GetFirstName().GetString(); 14: cout << " " << Edie.GetLastName().GetString(); 15: cout << "\nAddress: "; 16: cout << Edie.GetAddress().GetString(); 17: cout << "\nSalary: " ; 18: cout << Edie.GetSalary(); 19: cout << endl; 20: return 0; 21: } Output: 1: Creating Edie... 2: String(char*) constructor 3: String(char*) constructor 4: String(char*) constructor 5: Calling SetFirstName with char *... 6: String(char*) constructor 7: String destructor 8: Creating temporary string LstName... 9: String(char*) constructor 10: Name: Edythe Levine 11: Address: 1461 Shore Parkway 12: Salary: 20000 13: String destructor 14: String destructor 15: String destructor 16: String destructor
Analysis: Listing 15.3 uses the same class declarations as Listings 15.1 and 15.2. However, the cout statements have been uncommented. The output from Listing 15.3 has been numbered to make analysis easier.
On line 3 of Listing 15.3, the statement Creating Edie... is printed, as reflected on line 1 of the output. On line 4 an Employee object, Edie, is created with four parameters. The output reflects the constructor for String being called three times, as expected.
Line 6 prints an information statement, and then on line 7 is the statement Edie.SetFirstName("Edythe"). This statement causes a temporary string to be created from the character string "Edythe", as reflected on lines 6 and 7 of the output. Note that the temporary is destroyed immediately after it is used in the assignment statement.
On line 9, a String object is created in the body of the program. Here the programmer is doing explicitly what the compiler did implicitly on the previous statement. This time you see the constructor on line 9 of the output, but no destructor. This object will not be destroyed until it goes out of scope at the end of the function.
On lines 13-19, the strings in the employee object are destroyed as the Employee object falls out of scope, and the string LastName, created on line 9, is destroyed as well when it falls out of scope.
Listing 15.3 illustrates how the creation of one Employee object caused five string constructor calls. Listing 15.4 again rewrites the driver program. This time the print statements are not used, but the string static member variable ConstructorCount is uncommented and used.
Examination of Listing 15.1 shows that ConstructorCount is incremented each time a string constructor is called. The driver program in 15.4 calls the print functions, passing in the Employee object, first by reference and then by value. ConstructorCount keeps track of how many string objects are created when the employee is passed as a parameter.
NOTE: To compile this listing: 1. Uncomment lines 23, 39, 52, 64, 76, and 152 in Listing 15.1. 2. Edit Listing 15.2. Remove lines 68-84 and substitute Listing 15.4. 3. Add #include string.hpp as previously noted.
Listing 15.4. Passing by value
1: void PrintFunc(Employee); 2: void rPrintFunc(const Employee&); 3: 4: int main() 5: { 6: Employee Edie("Jane","Doe","1461 Shore Parkway", 20000); 7: Edie.SetSalary(20000); 8: Edie.SetFirstName("Edythe"); 9: String LastName("Levine"); 10: Edie.SetLastName(LastName); 11: 12: cout << "Constructor count: " ; 13: cout << String::ConstructorCount << endl; 14: rPrintFunc(Edie); 15: cout << "Constructor count: "; 16: cout << String::ConstructorCount << endl; 17: PrintFunc(Edie); 18: cout << "Constructor count: "; 19: cout << String::ConstructorCount << endl; 20: return 0; 21: } 22: void PrintFunc (Employee Edie) 23: { 24: 25: cout << "Name: "; 26: cout << Edie.GetFirstName().GetString(); 27: cout << " " << Edie.GetLastName().GetString(); 28: cout << ".\nAddress: "; 29: cout << Edie.GetAddress().GetString(); 30: cout << ".\nSalary: " ; 31: cout << Edie.GetSalary(); 32: cout << endl; 33: 34: } 35: 36: void rPrintFunc (const Employee& Edie) 37: { 38: cout << "Name: "; 39: cout << Edie.GetFirstName().GetString(); 40: cout << " " << Edie.GetLastName().GetString(); 41: cout << "\nAddress: "; 42: cout << Edie.GetAddress().GetString(); 43: cout << "\nSalary: " ; 44: cout << Edie.GetSalary(); 45: cout << endl; 46: } Output: String(char*) constructor String(char*) constructor String(char*) constructor String(char*) constructor String destructor String(char*) constructor Constructor count: 5 Name: Edythe Levine Address: 1461 Shore Parkway Salary: 20000 Constructor count: 5 String(String&) constructor String(String&) constructor String(String&) constructor Name: Edythe Levine. Address: 1461 Shore Parkway. Salary: 20000 String destructor String destructor String destructor Constructor count: 8 String destructor String destructor String destructor String destructor
Analysis: The output shows that five string objects were created as part of creating one Employee object. When the Employee object is passed to rPrintFunc() by reference, no additional Employee objects are created, and so no additional String objects are created. (They too are passed by reference.)
When, on line 14, the Employee object is passed to PrintFunc() by value, a copy of the Employee is created, and three more string objects are created (by calls to the copy constructor).
At times, one class wants to draw on some of the attributes of another class. For example, let's say you need to create a PartsCatalog class. The specification you've been given defines a PartsCatalog as a collection of parts; each part has a unique part number. The PartsCatalog does not allow duplicate entries, and does allow access by part number.
The listing for the Week in Review for Week 2 provides a LinkedList class. This LinkedList is well-tested and understood, and you'd like to build on that technology when making your PartsCatalog, rather than inventing it from scratch.
You could create a new PartsCatalog class and have it contain a LinkedList. The PartsCatalog could delegate management of the linked list to its contained LinkedList object.
An alternative would be to make the PartsCatalog derive from LinkedList and thereby inherit the properties of a LinkedList. Remembering, however, that public inheritance provides an is-a relationship, you should question whether a PartsCatalog really is a type of LinkedList.
One way to answer the question of whether PartsCatalog is a LinkedList is to assume that LinkedList is the base and PartsCatalog is the derived class, and then to ask these other questions:
Based on the answers to these questions, you must chose between public inheritance (the is-a relationship) and either private inheritance or containment.
New Term:
Why not derive PartsCatalog from LinkedList? The PartsCatalog isn't a LinkedList because LinkedLists are ordered collections and each member of the collection can repeat. The PartsCatalog has unique entries that are not ordered. The fifth member of the PartsCatalog is not part number 5.
Certainly it would have been possible to inherit publicly from PartsList and then override Insert() and the offset operators ([]) to do the right thing, but then you would have changed the essence of the PartsList class. Instead you'll build a PartsCatalog that has no offset operator, does not allow duplicates, and defines the operator+ to combine two sets.
The first way to accomplish this is with containment. The PartsCatalog will delegate list management to a contained LinkedList. Listing 15.5 illustrates this approach.
Listing 15.5. Delegating to a contained LinkedList.
0: #include <iostream.h> 1: 2: typedef unsigned long ULONG; 3: typedef unsigned short USHORT; 4: 5: 6: // **************** Part ************ 7: 8: // Abstract base class of parts 9: class Part 10: { 11: public: 12: Part():itsPartNumber(1) {} 13: Part(ULONG PartNumber): 14: itsPartNumber(PartNumber){} 15: virtual ~Part(){} 16: ULONG GetPartNumber() const 17: { return itsPartNumber; } 18: virtual void Display() const =0; 19: private: 20: ULONG itsPartNumber; 21: }; 22: 23: // implementation of pure virtual function so that 24: // derived classes can chain up 25: void Part::Display() const 26: { 27: cout << "\nPart Number: " << itsPartNumber << endl; 28: } 29: 30: // **************** Car Part ************ 31: 32: class CarPart : public Part 33: { 34: public: 35: CarPart():itsModelYear(94){} 36: CarPart(USHORT year, ULONG partNumber); 37: virtual void Display() const 38: { 39: Part::Display(); 40: cout << "Model Year: "; 41: cout << itsModelYear << endl; 42: } 43: private: 44: USHORT itsModelYear; 45: }; 46: 47: CarPart::CarPart(USHORT year, ULONG partNumber): 48: itsModelYear(year), 49: Part(partNumber) 50: {} 51: 52: 53: // **************** AirPlane Part ************ 54: 55: class AirPlanePart : public Part 56: { 57: public: 58: AirPlanePart():itsEngineNumber(1){}; 59: AirPlanePart 60: (USHORT EngineNumber, ULONG PartNumber); 61: virtual void Display() const 62: { 63: Part::Display(); 64: cout << "Engine No.: "; 65: cout << itsEngineNumber << endl; 66: } 67: private: 68: USHORT itsEngineNumber; 69: }; 70: 71: AirPlanePart::AirPlanePart 72: (USHORT EngineNumber, ULONG PartNumber): 73: itsEngineNumber(EngineNumber), 74: Part(PartNumber) 75: {} 76: 77: // **************** Part Node ************ 78: class PartNode 79: { 80: public: 81: PartNode (Part*); 82: ~PartNode(); 83: void SetNext(PartNode * node) 84: { itsNext = node; } 85: PartNode * GetNext() const; 86: Part * GetPart() const; 87: private: 88: Part *itsPart; 89: PartNode * itsNext; 90: }; 91: // PartNode Implementations... 92: 93: PartNode::PartNode(Part* pPart): 94: itsPart(pPart), 95: itsNext(0) 96: {} 97: 98: PartNode::~PartNode() 99: { 100: delete itsPart; 101: itsPart = 0; 102: delete itsNext; 103: itsNext = 0; 104: } 105: 106: // Returns NULL if no next PartNode 107: PartNode * PartNode::GetNext() const 108: { 109: return itsNext; 110: } 111: 112: Part * PartNode::GetPart() const 113: { 114: if (itsPart) 115: return itsPart; 116: else 117: return NULL; //error 118: } 119: 120: 121: 122: // **************** Part List ************ 123: class PartsList 124: { 125: public: 126: PartsList(); 127: ~PartsList(); 128: // needs copy constructor and operator equals! 129: void Iterate(void (Part::*f)()const) const; 130: Part* Find(ULONG & position, ULONG PartNumber) const; 131: Part* GetFirst() const; 132: void Insert(Part *); 133: Part* operator[](ULONG) const; 134: ULONG GetCount() const { return itsCount; } 135: static PartsList& GetGlobalPartsList() 136: { 137: return GlobalPartsList; 138: } 139: private: 140: PartNode * pHead; 141: ULONG itsCount; 142: static PartsList GlobalPartsList; 143: }; 144: 145: PartsList PartsList::GlobalPartsList; 146: 147: 148: PartsList::PartsList(): 149: pHead(0), 150: itsCount(0) 151: {} 152: 153: PartsList::~PartsList() 154: { 155: delete pHead; 156: } 157: 158: Part* PartsList::GetFirst() const 159: { 160: if (pHead) 161: return pHead->GetPart(); 162: else 163: return NULL; // error catch here 164: } 165: 166: Part * PartsList::operator[](ULONG offSet) const 167: { 168: PartNode* pNode = pHead; 169: 170: if (!pHead) 171: return NULL; // error catch here 172: 173: if (offSet > itsCount) 174: return NULL; // error 175: 176: for (ULONG i=0;i<offSet; i++) 177: pNode = pNode->GetNext(); 178: 179: return pNode->GetPart(); 180: } 181: 182: Part* PartsList::Find( 183: ULONG & position, 184: ULONG PartNumber) const 185: { 186: PartNode * pNode = 0; 187: for (pNode = pHead, position = 0; 188: pNode!=NULL; 189: pNode = pNode->GetNext(), position++) 190: { 191: if (pNode->GetPart()->GetPartNumber() == PartNumber) 192: break; 193: } 194: if (pNode == NULL) 195: return NULL; 196: else 197: return pNode->GetPart(); 198: } 199: 200: void PartsList::Iterate(void (Part::*func)()const) const 201: { 202: if (!pHead) 203: return; 204: PartNode* pNode = pHead; 205: do 206: (pNode->GetPart()->*func)(); 207: while (pNode = pNode->GetNext()); 208: } 209: 210: void PartsList::Insert(Part* pPart) 211: { 212: PartNode * pNode = new PartNode(pPart); 213: PartNode * pCurrent = pHead; 214: PartNode * pNext = 0; 215: 216: ULONG New = pPart->GetPartNumber(); 217: ULONG Next = 0; 218: itsCount++; 219: 220: if (!pHead) 221: { 222: pHead = pNode; 223: return; 224: } 225: 226: // if this one is smaller than head 227: // this one is the new head 228: if (pHead->GetPart()->GetPartNumber() > New) 229: { 230: pNode->SetNext(pHead); 231: pHead = pNode; 232: return; 233: } 234: 235: for (;;) 236: { 237: // if there is no next, append this new one 238: if (!pCurrent->GetNext()) 239: { 240: pCurrent->SetNext(pNode); 241: return; 242: } 243: 244: // if this goes after this one and before the next 245: // then insert it here, otherwise get the next 246: pNext = pCurrent->GetNext(); 247: Next = pNext->GetPart()->GetPartNumber(); 248: if (Next > New) 249: { 250: pCurrent->SetNext(pNode); 251: pNode->SetNext(pNext); 252: return; 253: } 254: pCurrent = pNext; 255: } 256: } 257: 258: 259: 260: class PartsCatalog 261: { 262: public: 263: void Insert(Part *); 264: ULONG Exists(ULONG PartNumber); 265: Part * Get(int PartNumber); 266: operator+(const PartsCatalog &); 267: void ShowAll() { thePartsList.Iterate(Part::Display); } 268: private: 269: PartsList thePartsList; 270: }; 271: 272: void PartsCatalog::Insert(Part * newPart) 273: { 274: ULONG partNumber = newPart->GetPartNumber(); 275: ULONG offset; 276: 277: if (!thePartsList.Find(offset, partNumber)) 278: 279: thePartsList.Insert(newPart); 280: else 281: { 282: cout << partNumber << " was the "; 283: switch (offset) 284: { 285: case 0: cout << "first "; break; 286: case 1: cout << "second "; break; 287: case 2: cout << "third "; break; 288: default: cout << offset+1 << "th "; 289: } 290: cout << "entry. Rejected!\n"; 291: } 292: } 293: 294: ULONG PartsCatalog::Exists(ULONG PartNumber) 295: { 296: ULONG offset; 297: thePartsList.Find(offset,PartNumber); 298: return offset; 299: } 300: 301: Part * PartsCatalog::Get(int PartNumber) 302: { 303: ULONG offset; 304: Part * thePart = thePartsList.Find(offset, PartNumber); 305: return thePart; 306: } 307: 308: 309: int main() 310: { 311: PartsCatalog pc; 312: Part * pPart = 0; 313: ULONG PartNumber; 314: USHORT value; 315: ULONG choice; 316: 317: while (1) 318: { 319: cout << "(0)Quit (1)Car (2)Plane: "; 320: cin >> choice; 321: 322: if (!choice) 323: break; 324: 325: cout << "New PartNumber?: "; 326: cin >> PartNumber; 327: 328: if (choice == 1) 329: { 330: cout << "Model Year?: "; 331: cin >> value; 332: pPart = new CarPart(value,PartNumber); 333: } 334: else 335: { 336: cout << "Engine Number?: "; 337: cin >> value; 338: pPart = new AirPlanePart(value,PartNumber); 339: } 340: pc.Insert(pPart); 341: } 342: pc.ShowAll(); 343: return 0; 344: } Output: (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 1234 Model Year?: 94 (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 4434 Model Year?: 93 (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 1234 Model Year?: 94 1234 was the first entry. Rejected! (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 2345 Model Year?: 93 (0)Quit (1)Car (2)Plane: 0 Part Number: 1234 Model Year: 94 Part Number: 2345 Model Year: 93 Part Number: 4434 Model Year: 93
Analysis: Listing 15.7 reproduces the interface to the Part, PartNode, and PartList classes from Week 2 in Review, but to save room it does not reproduce the implementation of their methods.
A new class, PartsCatalog, is declared on lines 260-270. PartsCatalog has a PartsList as its data member, to which it delegates list management. Another way to say this is that the PartsCatalog is implemented in terms of this PartsList.
Note that clients of the PartsCatalog do not have access to the PartsList directly. The interface is through the PartsCatalog, and as such the behavior of the PartsList is dramatically changed. For example, the PartsCatalog::Insert() method does not allow duplicate entries into the PartsList.
The implementation of PartsCatalog::Insert() starts on line 272. The Part that is passed in as a parameter is asked for the value of its itsPartNumber member variable. This value is fed to the PartsList's Find() method, and if no match is found the number is inserted; otherwise an informative error message is printed.
Note that PartsCatalog does the actual insert by calling Insert() on its member variable, pl, which is a PartsList. The mechanics of the actual insertion and the maintenance of the linked list, as well as searching and retrieving from the linked list, are maintained in the contained PartsList member of PartsCatalog. There is no reason for PartsCatalog to reproduce this code; it can take full advantage of the well-defined interface.
This is the essence of reusability within C++: PartsCatalog can reuse the PartsList code, and the designer of PartsCatalog is free to ignore the implementation details of PartsList. The interface to PartsList (that is, the class declaration) provides all the information needed by the designer of the PartsCatalog class.
If PartsCatalog needed access to the protected members of LinkedList (in this case there are none), or needed to override any of the LinkedList methods, then PartsCatalog would be forced to inherit from PartsList.
Since a PartsCatalog is not a PartsList object, and since you don't want to expose the entire set of functionality of PartsList to clients of PartsCatalog, you need to use private inheritance.
The first thing to know about private inheritance is that all of the base member variables and functions are treated as if they were declared to be private, regardless of their actual access level in the base. Thus, to any function that is not a member function of PartsCatalog, every function inherited from PartsList is inaccessible. This is critical: private inheritance does not involve inheriting interface, just implementation.
To clients of the PartsCatalog class, the PartsList class is invisible. None of its interface is available: you can't call any of its methods. You can call PartsCatalog methods, however, and they can access all of LinkedLists, because they are derived from LinkedLists.
The important thing here is that the PartsCatalog isn't a PartsList, as would have been implied by public inheritance. It is implemented in terms of a PartsList, just as would have been the case with containment. The private inheritance is just a convenience.
Listing 15.6 demonstrates the use of private inheritance by rewriting the PartsCatalog class as privately derived from PartsList.
NOTE: To compile this program, replace lines 260-344 of Listing 15.5 with Listing 15.6 and recompile.
Listing 15.6. Private inheritance.
1: //listing 15.6 demonstrates private inheritance 2: 3: //rewrites PartsCatalog from listing 15.5 4: 5: //see attached notes on compiling 6: 7: class PartsCatalog : private PartsList 8: { 9: public: 10: void Insert(Part *); 11: ULONG Exists(ULONG PartNumber); 12: Part * Get(int PartNumber); 13: operator+(const PartsCatalog &); 14: void ShowAll() { Iterate(Part::Display); } 15: private: 16: }; 17: 18: void PartsCatalog::Insert(Part * newPart) 19: { 20: ULONG partNumber = newPart->GetPartNumber(); 21: ULONG offset; 22: 23: if (!Find(offset, partNumber)) 24: PartsList::Insert(newPart); 25: else 26: { 27: cout << partNumber << " was the "; 28: switch (offset) 29: { 30: case 0: cout << "first "; break; 31: case 1: cout << "second "; break; 32: case 2: cout << "third "; break; 33: default: cout << offset+1 << "th "; 34: } 35: cout << "entry. Rejected!\n"; 36: } 37: } 38: 39: ULONG PartsCatalog::Exists(ULONG PartNumber) 40: { 41: ULONG offset; 42: Find(offset,PartNumber); 43: return offset; 44: } 45: 46: Part * PartsCatalog::Get(int PartNumber) 47: { 48: ULONG offset; 49: return (Find(offset, PartNumber)); 50: 51: } 52: 53: int main() 54: { 55: PartsCatalog pc; 56: Part * pPart = 0; 57: ULONG PartNumber; 58: USHORT value; 59: ULONG choice; 60: 61: while (1) 62: { 63: cout << "(0)Quit (1)Car (2)Plane: "; 64: cin >> choice; 65: 66: if (!choice) 67: break; 68: 69: cout << "New PartNumber?: "; 70: cin >> PartNumber; 71: 72: if (choice == 1) 73: { 74: cout << "Model Year?: "; 75: cin >> value; 76: pPart = new CarPart(value,PartNumber); 77: } 78: else 79: { 80: cout << "Engine Number?: "; 81: cin >> value; 82: pPart = new AirPlanePart(value,PartNumber); 83: } 84: pc.Insert(pPart); 85: } 86: pc.ShowAll(); 87: return 0; 88: } Output: (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 1234 Model Year?: 94 (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 4434 Model Year?: 93 (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 1234 Model Year?: 94 1234 was the first entry. Rejected! (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 2345 Model Year?: 93 (0)Quit (1)Car (2)Plane: 0 Part Number: 1234 Model Year: 94 Part Number: 2345 Model Year: 93 Part Number: 4434 Model Year: 93
Analysis: Listing 15.6 shows only the changed interface to PartsCatalog and the rewritten driver program. The interfaces to the other classes are unchanged from Listing 15.5.
On line 7 of Listing 15.6, PartsCatalog is declared to derive privately from PartsList. The interface to PartsCatalog doesn't change from Listing 15.5, though of course it no longer needs an object of type PartsList as member data.
The PartsCatalog ShowAll() function calls PartsList Iterate() with the appropriate pointer to member function of class Part. ShowAll() acts as a public interface to Iterate(), providing the correct information but preventing client classes from calling Iterate() dir-ectly. Although PartsList might allow other functions to be passed to Iterate(), PartsCatalog does not.
The Insert() function has changed as well. Note, on line 23, that Find() is now called directly, because it is inherited from the base class. The call on line 24 to Insert() must be fully qualified, of course, or it would endlessly recurse into itself.
In short, when methods of PartsCatalog want to call PartsList methods, they may do so directly. The only exception is when PartsCatalog has overridden the method and the PartsList version is needed, in which case the function name must be qualified fully.
Private inheritance allows the PartsCatalog to inherit what it can use, but still provide mediated access to Insert and other methods to which client classes should not have direct access.
DO inherit publicly when the derived object is a kind of the base class. DO use containment when you want to delegate functionality to another class, but you don't need access to its protected members. DO use private inheritance when you need to implement one class in terms of another, and you need access to the base class's protected members. DON'T use private inheritance when you need to use more than one of the base class. You must use containment. For example, if PartsCatalog needed two PartsLists, you could not have used private inheritance. DON'T use public inheritance when members of the base class should not be available to clients of the derived class.
Sometimes you will create classes together, as a set. For example, PartNode and PartsList were tightly coupled, and it would have been convenient if PartsList could have read PartNode's Part pointer, itsPart, directly.
You wouldn't want to make itsPart public, or even protected, because this is an implementation detail of PartNode and you want to keep it private. You do want to expose it to PartsList, however.
If you want to expose your private member data or functions to another class, you must declare that class to be a friend. This extends the interface of your class to include the friend class.
Once PartsNode declares PartsList to be a friend, all of PartsNode's member data and functions are public as far as PartsList is concerned.
It is important to note that friendship cannot be transferred. Just because you are my friend and Joe is your friend doesn't mean Joe is my friend. Friendship is not inherited either. Again, just because you are my friend and I'm willing to share my secrets with you doesn't mean I'm willing to share my secrets with your children.
Finally, friendship is not commutative. Assigning Class One to be a friend of Class Two does not make Class Two a friend of Class One. Just because you are willing to tell me your secrets doesn't mean I am willing to tell you mine.
Listing 15.7 illustrates friendship by rewriting the example from Listing 15.6, making PartsList a friend of PartNode. Note that this does not make PartNode a friend of PartsList.
Listing 15.7. Friend class illustrated.
0: #include <iostream.h> 1: 2: typedef unsigned long ULONG; 3: typedef unsigned short USHORT; 4: 5: 6: // **************** Part ************ 7: 8: // Abstract base class of parts 9: class Part 10: { 11: public: 12: Part():itsPartNumber(1) {} 13: Part(ULONG PartNumber): 14: itsPartNumber(PartNumber){} 15: virtual ~Part(){} 16: ULONG GetPartNumber() const 17: { return itsPartNumber; } 18: virtual void Display() const =0; 19: private: 20: ULONG itsPartNumber; 21: }; 22: 23: // implementation of pure virtual function so that 24: // derived classes can chain up 25: void Part::Display() const 26: { 27: cout << "\nPart Number: "; 28: cout << itsPartNumber << endl; 29: } 30: 31: // **************** Car Part ************ 32: 33: class CarPart : public Part 34: { 35: public: 36: CarPart():itsModelYear(94){} 37: CarPart(USHORT year, ULONG partNumber); 38: virtual void Display() const 39: { 40: Part::Display(); 41: cout << "Model Year: "; 42: cout << itsModelYear << endl; 43: } 44: private: 45: USHORT itsModelYear; 46: }; 47: 48: CarPart::CarPart(USHORT year, ULONG partNumber): 49: itsModelYear(year), 50: Part(partNumber) 51: {} 52: 53: 54: // **************** AirPlane Part ************ 55: 56: class AirPlanePart : public Part 57: { 58: public: 59: AirPlanePart():itsEngineNumber(1){}; 60: AirPlanePart 61: (USHORT EngineNumber, ULONG PartNumber); 62: virtual void Display() const 63: { 64: Part::Display(); 65: cout << "Engine No.: "; 66: cout << itsEngineNumber << endl; 67: } 68: private: 69: USHORT itsEngineNumber; 70: }; 71: 72: AirPlanePart::AirPlanePart 73: (USHORT EngineNumber, ULONG PartNumber): 74: itsEngineNumber(EngineNumber), 75: Part(PartNumber) 76: {} 77: 78: // **************** Part Node ************ 79: class PartNode 80: { 81: public: 82: friend class PartsList; 83: PartNode (Part*); 84: ~PartNode(); 85: void SetNext(PartNode * node) 86: { itsNext = node; } 87: PartNode * GetNext() const; 88: Part * GetPart() const; 89: private: 90: Part *itsPart; 91: PartNode * itsNext; 92: }; 93: 94: 95: PartNode::PartNode(Part* pPart): 96: itsPart(pPart), 97: itsNext(0) 98: {} 99: 100: PartNode::~PartNode() 101: { 102: delete itsPart; 103: itsPart = 0; 104: delete itsNext; 105: itsNext = 0; 106: } 107: 108: // Returns NULL if no next PartNode 109: PartNode * PartNode::GetNext() const 110: { 111: return itsNext; 112: } 113: 114: Part * PartNode::GetPart() const 115: { 116: if (itsPart) 117: return itsPart; 118: else 119: return NULL; //error 120: } 121: 122: 123: // **************** Part List ************ 124: class PartsList 125: { 126: public: 127: PartsList(); 128: ~PartsList(); 129: // needs copy constructor and operator equals! 130: void Iterate(void (Part::*f)()const) const; 131: Part* Find(ULONG & position, ULONG PartNumber) const; 132: Part* GetFirst() const; 133: void Insert(Part *); 134: Part* operator[](ULONG) const; 135: ULONG GetCount() const { return itsCount; } 136: static PartsList& GetGlobalPartsList() 137: { 138: return GlobalPartsList; 139: } 140: private: 141: PartNode * pHead; 142: ULONG itsCount; 143: static PartsList GlobalPartsList; 144: }; 145: 146: PartsList PartsList::GlobalPartsList; 147: 148: // Implementations for Lists... 149: 150: PartsList::PartsList(): 151: pHead(0), 152: itsCount(0) 153: {} 154: 155: PartsList::~PartsList() 156: { 157: delete pHead; 158: } 159: 160: Part* PartsList::GetFirst() const 161: { 162: if (pHead) 163: return pHead->itsPart; 164: else 165: return NULL; // error catch here 166: } 167: 168: Part * PartsList::operator[](ULONG offSet) const 169: { 170: PartNode* pNode = pHead; 171: 172: if (!pHead) 173: return NULL; // error catch here 174: 175: if (offSet > itsCount) 176: return NULL; // error 177: 178: for (ULONG i=0;i<offSet; i++) 179: pNode = pNode->itsNext; 180: 181: return pNode->itsPart; 182: } 183: 184: Part* PartsList::Find(ULONG & position, ULONG PartNumber) const 185: { 186: PartNode * pNode = 0; 187: for (pNode = pHead, position = 0; 188: pNode!=NULL; 189: pNode = pNode->itsNext, position++) 190: { 191: if (pNode->itsPart->GetPartNumber() == PartNumber) 192: break; 193: } 194: if (pNode == NULL) 195: return NULL; 196: else 197: return pNode->itsPart; 198: } 199: 200: void PartsList::Iterate(void (Part::*func)()const) const 201: { 202: if (!pHead) 203: return; 204: PartNode* pNode = pHead; 205: do 206: (pNode->itsPart->*func)(); 207: while (pNode = pNode->itsNext); 208: } 209: 210: void PartsList::Insert(Part* pPart) 211: { 212: PartNode * pNode = new PartNode(pPart); 213: PartNode * pCurrent = pHead; 214: PartNode * pNext = 0; 215: 216: ULONG New = pPart->GetPartNumber(); 217: ULONG Next = 0; 218: itsCount++; 219: 220: if (!pHead) 221: { 222: pHead = pNode; 223: return; 224: } 225: 226: // if this one is smaller than head 227: // this one is the new head 228: if (pHead->itsPart->GetPartNumber() > New) 229: { 230: pNode->itsNext = pHead; 231: pHead = pNode; 232: return; 233: } 234: 235: for (;;) 236: { 237: // if there is no next, append this new one 238: if (!pCurrent->itsNext) 239: { 240: pCurrent->itsNext = pNode; 241: return; 242: } 243: 244: // if this goes after this one and before the next 245: // then insert it here, otherwise get the next 246: pNext = pCurrent->itsNext; 247: Next = pNext->itsPart->GetPartNumber(); 248: if (Next > New) 249: { 250: pCurrent->itsNext = pNode; 251: pNode->itsNext = pNext; 252: return; 253: } 254: pCurrent = pNext; 255: } 256: } 257: 258: class PartsCatalog : private PartsList 259: { 260: public: 261: void Insert(Part *); 262: ULONG Exists(ULONG PartNumber); 263: Part * Get(int PartNumber); 264: operator+(const PartsCatalog &); 265: void ShowAll() { Iterate(Part::Display); } 266: private: 267: }; 268: 269: void PartsCatalog::Insert(Part * newPart) 270: { 271: ULONG partNumber = newPart->GetPartNumber(); 272: ULONG offset; 273: 274: if (!Find(offset, partNumber)) 275: PartsList::Insert(newPart); 276: else 277: { 278: cout << partNumber << " was the "; 279: switch (offset) 280: { 281: case 0: cout << "first "; break; 282: case 1: cout << "second "; break; 283: case 2: cout << "third "; break; 284: default: cout << offset+1 << "th "; 285: } 286: cout << "entry. Rejected!\n"; 287: } 288: } 289: 290: ULONG PartsCatalog::Exists(ULONG PartNumber) 291: { 292: ULONG offset; 293: Find(offset,PartNumber); 294: return offset; 295: } 296: 297: Part * PartsCatalog::Get(int PartNumber) 298: { 299: ULONG offset; 300: return (Find(offset, PartNumber)); 301: 302: } 303: 304: int main() 305: { 306: PartsCatalog pc; 307: Part * pPart = 0; 308: ULONG PartNumber; 309: USHORT value; 310: ULONG choice; 311: 312: while (1) 313: { 314: cout << "(0)Quit (1)Car (2)Plane: "; 315: cin >> choice; 316: 317: if (!choice) 318: break; 319: 320: cout << "New PartNumber?: "; 321: cin >> PartNumber; 322: 323: if (choice == 1) 324: { 325: cout << "Model Year?: "; 326: cin >> value; 327: pPart = new CarPart(value,PartNumber); 328: } 329: else 330: { 331: cout << "Engine Number?: "; 332: cin >> value; 333: pPart = new AirPlanePart(value,PartNumber); 334: } 335: pc.Insert(pPart); 336: } 337: pc.ShowAll(); 338: return 0; 339: } Output: (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 1234 Model Year?: 94 (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 4434 Model Year?: 93 (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 1234 Model Year?: 94 1234 was the first entry. Rejected! (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 2345 Model Year?: 93 (0)Quit (1)Car (2)Plane: 0 Part Number: 1234 Model Year: 94 Part Number: 2345 Model Year: 93 Part Number: 4434 Model Year: 93
Analysis: On line 82, the class PartsList is declared to be a friend to the PartNode class. Because PartsList has not yet been declared, the compiler would complain that this type is not known.
This listing places the friend declaration in the public section, but this is not required; it can be put anywhere in the class declaration without changing the meaning of the statement. Because of this statement, all the private member data and functions are available to any member function of class PartsList.
On line 160, the implementation of the member function GetFirst() reflects this change. Rather than returning pHead->GetPart, this function can now return the otherwise private member data by writing pHead->itsPart. Similarly, the Insert() function can now write pNode->itsNext = pHead, rather than writing pNode->SetNext(pHead).
Admittedly these are trivial changes, and there is not a good enough reason to make PartsList a friend of PartNode, but they do serve to illustrate how the keyword friend works.
Declarations of friend classes should be used with extreme caution. If two classes are inextricably entwined, and one must frequently access data in the other, there may be good reason to use this declaration. But use it sparingly; it is often just as easy to use the public accessor methods, and doing so allows you to change one class without having to recompile the other.
NOTE: You will often hear novice C++ programmers complain that friend declarations "undermine" the encapsulation so important to object-oriented programming. This is, frankly, errant nonsense. The friend declaration makes the declared friend part of the class interface, and is no more an undermining of encapsulation than is public derivation.
Declare one class to be a friend of another by putting the word friend into the class granting the access rights. That is, I can declare you to be my friend, but you can't declare yourself to be my friend. Example:
class PartNode{ public: friend class PartList; // declares PartList to be a friend of PartNode };
At times you will want to grant this level of access not to an entire class, but only to one or two functions of that class. You can do this by declaring the member functions of the other class to be friends, rather than declaring the entire class to be a friend. In fact, you can declare any function, whether or not it is a member function of another class, to be a friend function.
Listing 15.1 provided a String class that overrode the operator+. It also provided a constructor that took a constant character pointer, so that string objects could be created from C-style strings. This allowed you to create a string and add to it with a C-style string.
NOTE: C-style strings are null-terminated character arrays, such as char myString[] = "Hello World."
What you could not do, however, was create a C-style string (a character string) and add to it using a string object, as shown in this example:
char cString[] = {"Hello"}; String sString(" World"); String sStringTwo = cString + sString; //error!
C-style strings don't have an overloaded operator+. As discussed on Day 10, "Advanced Functions," when you say cString + sString; what you are really calling is cString.operator+(sString). Since you can't call operator+() on a C-style string, this causes a compile-time error.
You can solve this problem by declaring a friend function in String, which overloads operator+ but takes two string objects. The C-style string will be converted to a string object by the appropriate constructor, and then operator+ will be called using the two string objects.
NOTE: To compile this listing, copy lines 33-123 from Listing 15.1 after line 33 of Listing 15.8.
Listing 15.8. Friendly operator+.
1: //Listing 15.8 - friendly operators 2: 3: #include <iostream.h> 4: #include <string.h> 5: 6: // Rudimentary string class 7: class String 8: { 9: public: 10: // constructors 11: String(); 12: String(const char *const); 13: String(const String &); 14: ~String(); 15: 16: // overloaded operators 17: char & operator[](int offset); 18: char operator[](int offset) const; 19: String operator+(const String&); 20: friend String operator+(const String&, const String&); 21: void operator+=(const String&); 22: String & operator= (const String &); 23: 24: // General accessors 25: int GetLen()const { return itsLen; } 26: const char * GetString() const { return itsString; } 27: 28: private: 29: String (int); // private constructor 30: char * itsString; 31: unsigned short itsLen; 32: }; 33: 34: // creates a new string by adding current 35: // string to rhs 36: String String::operator+(const String& rhs) 37: { 38: int totalLen = itsLen + rhs.GetLen(); 39: String temp(totalLen); 40: for (int i = 0; i<itsLen; i++) 41: temp[i] = itsString[i]; 42: for (int j = 0; j<rhs.GetLen(); j++, i++) 43: temp[i] = rhs[j]; 44: temp[totalLen]='\0'; 45: return temp; 46: } 47: 48: // creates a new string by adding 49: // one string to another 50: String operator+(const String& lhs, const String& rhs) 51: { 52: int totalLen = lhs.GetLen() + rhs.GetLen(); 53: String temp(totalLen); 54: for (int i = 0; i<lhs.GetLen(); i++) 55: temp[i] = lhs[i]; 56: for (int j = 0; j<rhs.GetLen(); j++, i++) 57: temp[i] = rhs[j]; 58: temp[totalLen]='\0'; 59: return temp; 60: } 61: 62: int main() 63: { 64: String s1("String One "); 65: String s2("String Two "); 66: char *c1 = { "C-String One " } ; 67: String s3; 68: String s4; 69: String s5; 70: 71: cout << "s1: " << s1.GetString() << endl; 72: cout << "s2: " << s2.GetString() << endl; 73: cout << "c1: " << c1 << endl; 74: s3 = s1 + s2; 75: cout << "s3: " << s3.GetString() << endl; 76: s4 = s1 + c1; 77: cout << "s4: " << s4.GetString() << endl; 78: s5 = c1 + s1; 79: cout << "s5: " << s5.GetString() << endl; 80: return 0; 81: } Output: s1: String One s2: String Two c1: C-String One s3: String One String Two s4: String One C-String One s5: C-String One String Two
Analysis: The implementation of all of the string methods except operator+ are unchanged from Listing 15.1, and so are left out of this listing. On line 20, a new operator+ is overloaded to take two constant string references and to return a string, and this function is declared to be a friend.
Note that this operator+ is not a member function of this or any other class. It is declared within the declaration of the String class only so that it can be made a friend, but because it is declared no other function prototype is needed.
The implementation of this operator+ is on lines 50-60. Note that it is similar to the earlier operator+, except that it takes two strings and accesses them both through their public accessor methods.
The driver program demonstrates the use of this function on line 78, where operator+ is now called on a C-style string!
Declare a function to be a friend by using the keyword friend and then the full specification of the function. Declaring a function to be a friend does not give the friend function access to your this pointer, but it does provide full access to all private and protected member data and functions. Example
class PartNode { // make another class's member function a _friend friend void PartsList::Insert(Part *); // make a global function a friend }; friend int SomeFunction();
You are finally ready to give your String class the ability to use cout like any other type. Until now, when you've wanted to print a string, you've been forced to write the following:
cout << theString.GetString();
What you would like to do is write this:
cout << theString;
To accomplish this, you must override operator<<(). Day 16, "Streams," presents the ins and outs (cins and couts?) of working with iostreams; for now Listing 15.9 illustrates how operator<< can be overloaded using a friend function.
NOTE: To compile this listing, copy lines 33-153 from Listing 15.1 after line 31 of Listing 15.9.
Listing 15.9. Overloading operator<<().
1: #include <iostream.h> 2: #include <string.h> 3: 4: class String 5: { 6: public: 7: // constructors 8: String(); 9: String(const char *const); 10: String(const String &); 11: ~String(); 12: 13: // overloaded operators 14: char & operator[](int offset); 15: char operator[](int offset) const; 16: String operator+(const String&); 17: void operator+=(const String&); 18: String & operator= (const String &); 19: friend ostream& operator<< 20: ( ostream& theStream,String& theString); 21: // General accessors 22: int GetLen()const { return itsLen; } 23: const char * GetString() const { return itsString; } 24: // static int ConstructorCount; 25: private: 26: String (int); // private constructor 27: char * itsString; 28: unsigned short itsLen; 29: }; 30: 31: ostream& operator<< 32: ( ostream& theStream,String& theString) 33: { 34: theStream << theString.GetString(); 35: return theStream; 36: } 37: int main() 38: { 39: String theString("Hello world."); 40: cout << theString; 41: return 0; 42: } Output: Hello world.
Analysis: To save space, the implementation of all of String's methods is left out, as they are unchanged from the previous examples.
On line 19, operator<< is declared to be a friend function that takes an ostream reference and a String reference and then returns an ostream reference. Note that this is not a member function of String. It returns a reference to an ostream so that you can concatenate calls to operator<<, such as this:
cout << "myAge: " << itsAge << " years.";
The implementation of this friend function is on lines 32-35. All this really does is hide the implementation details of feeding the string to the ostream, and that is just as it should be. You'll see more about overloading this operator and operator>> on Day 16.
Today you saw how to delegate functionality to a contained object. You also saw how to implement one class in terms of another by using either containment or private inheritance. Containment is restricted in that the new class does not have access to the protected members of the contained class, and it cannot override the member functions of the contained object. Containment is simpler to use than private inheritance, and should be used when possible.
You also saw how to declare both friend functions and friend classes. Using a friend function, you saw how to overload the extraction operator, to allow your new classes to use cout just as the built-in classes do.
Remember that public inheritance expresses is-a, containment expresses has-a, and private inheritance expresses implemented in terms of. The relationship delegates to can be expressed using either containment or private inheritance, though containment is more common.
The Workshop contains quiz questions to help solidify your understanding of the material covered and exercises to provide you with experience in using what you've learned. Try to answer the quiz and exercise questions before checking the answers in Appendix D, and make sure you understand the answers before going to the next chapter.
1: #include <iostream.h> 2: 3: class Animal; 4: 5: void setValue(Animal& , int); 6: 7: 8: class Animal 9: { 10: public: 11: int GetWeight()const { return itsWeight; } 12: int GetAge() const { return itsAge; } 13: private: 14: int itsWeight; 15: int itsAge; 16: }; 17: 18: void setValue(Animal& theAnimal, int theWeight) 19: { 20: friend class Animal; 21: theAnimal.itsWeight = theWeight; 22: } 23: 24: int main() 25: { 26: Animal peppy; 27: setValue(peppy,5);28: }
1: #include <iostream.h> 2: 3: class Animal; 4: 5: void setValue(Animal& , int); 6: void setValue(Animal& ,int,int); 7: 8: class Animal 9: { 10: friend void setValue(Animal& ,int);11: private: 12: int itsWeight; 13: int itsAge; 14: }; 15: 16: void setValue(Animal& theAnimal, int theWeight) 17: { 18: theAnimal.itsWeight = theWeight; 19: } 20: 21: 22: void setValue(Animal& theAnimal, int theWeight, int theAge) 23: { 24: theAnimal.itsWeight = theWeight; 25: theAnimal.itsAge = theAge; 26: } 27: 28: int main() 29: { 30: Animal peppy; 31: setValue(peppy,5); 32: setValue(peppy,7,9); 33: }