Site hosted by Angelfire.com: Build your free website today!

<< Back Index Next >>

Day 15

Advanced Inheritance

So far you have worked with single and multiple inheritance to create is-a relationships. Today you will learn

Containment

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.

Accessing Members of the Contained Class

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.

Filtering Access to Contained Members

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.

Cost of Containment

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.

Copying by Value

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).

Implementation in Terms of Inheritance/Containment Versus Delegation

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:

1. Is there anything in the base class that should not be in the derived? For example, does the LinkedList base class have functions that are inappropriate for the PartsCatalog
class? If so, you probably don't want public inheritance.

2. Might the class you are creating have more than one of the base? For example, might a PartsCatalog need two LinkedLists in each object? If it might, you almost certainly want to use containment.

3. Do you need to inherit from the base class so that you can take advantage of virtual functions or access protected members? If so, you must use inheritance, public or private.

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:
Contained --An object declared as a member of another class contained by that class.

Delegation --Using the attributes of a contained class to accomplish functions not otherwise available to the containing class.

Implemented in terms of --Building one class on the capabilities of another without using public inheritance.

Delegation

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.

Private Inheritance

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.

Friend Classes

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.

Friend Class

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
};

Friend Functions

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.

Friend Functions and Operator Overloading

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!

Friend Functions

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();              

Overloading the Insertion Operator

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.

Summary

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.

Q&A

Q. Why is it so important to distinguish between is-a, has-a, and implemented in terms of?

A. The point of C++ is to implement well-designed, object-oriented programs. Keeping these relationships straight helps to ensure that your design corresponds to the reality of what you are modeling. Furthermore, a well-understood design will more likely be reflected in well-designed code.

Q. Why is containment preferred over private inheritance?

A. The challenge in modern programming is to cope with complexity. The more you can use objects as black boxes, the fewer details you have to worry about and the more complexity you can manage. Contained classes hide their details; private inheritance exposes the implementation details.

Q. Why not make all classes friends of all the classes they use?

A. Making one class a friend of another exposes the implementation details and reduces encapsulation. The ideal is to keep as many of the details of each class hidden from all other classes as possible.

Q. If a function is overloaded, do you need to declare each form of the function to be a friend?

A. Yes, if you overload a function and declare it to be a friend of another class, you must declare friend for each form that you wish to grant this access to.

Workshop

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.

Quiz

1. How do you establish an is-a relationship?

2.
How do you establish a has-a relationship?

3.
What is the difference between containment and delegation?

4.
What is the difference between delegation and implemented in terms of?

5.
What is a friend function?

6.
What is a friend class?

7.
If Dog is a friend of Boy, is Boy a friend of Dog?

8.
If Dog is a friend of Boy, and Terrier derives from Dog, is Terrier a friend of Boy?

9.
If Dog is a friend of Boy and Boy is a friend of House, is Dog a friend of House?

10
. Where must the declaration of a friend function appear?

Exercises

1. Show the declaration of a class, Animal, that contains a datamember that is a string object.

2.
Show the declaration of a class, BoundedArray, that is an array.

3.
Show the declaration of a class, Set, that is declared in terms of an array.

4.
Modify Listing 15.1 to provide the String class with an extraction operator (>>).

5.
BUG BUSTERS: What is wrong with this program?

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:    }
6. Fix the listing in Exercise 5 so it compiles.

7.
BUG BUSTERS: What is wrong with this code?
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:    }
8. Fix Exercise 7 so it compiles.


<< Back Index Next >>