Day 15
Day 15
Advanced Inheritance
So far you have worked with single and multiple inheritance to create is-a relationships.
Today you will learn
- What containment is and how to model it.
- What delegation is and how to model it.
- How to implement one class in terms of another.
- How to use private inheritance.
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: }