So far, you have worked with single and multiple inheritance to create is-a relationships.
Today, you will learn
• What aggregation is and how to model it (the has-a relationship)
• What delegation is and how to model it
• How to implement one class in terms of another
• How to use private inheritance
You have seen in previous examples that it is possible for the member data of a class to contain objects of other class types. This is often called aggregation, or the has-a relationship.
As an illustration, consider classes such as a Name
class and an Address
class:
Class Name
{
// Class information for Name
};
Class Address
{
// Class information for Address
};
As an illustration of aggregation, these two classes could be included as part of an Employee
class:
Class Employee
{
Name EmpName;
Address EmpAddress;
// Any other employee class stuff...
}
Thus, an Employee
class contains member variables for a name and for an address (Employee
has-a Name
and Employee
has-an Address
).
A more complex example is presented in Listing 16.1. This is an incomplete, but still useful, String
class, not unlike the String
class created on Day 13, “Managing Arrays and Strings.” This listing does not produce any output. Instead, Listing 16.1 will be used with later listings.
Listing 16.1. The String
Class
Note
Put the code from Listing 16.1 into a file called MyString.hpp
. Then, any time you need the String
class, you can include Listing 16.1 by using #include "MyString.hpp"
, such as in this listing. You might notice a number of commented lines in this listing. The purpose of these lines are explained throughout today’s lesson.
Listing 16.1 provides a String
class much like the one used in Listing 13.12 of Day 13. The significant difference here is that the constructors and a few other functions in Listing 13.12 have print
statements to show their use, which are currently commented out in Listing 16.1. These functions will be used in later examples.
On line 25, the static member variable ConstructorCount
is declared, and on line 156 it is initialized. This variable is incremented in each string constructor. All this is currently commented out; it will be used in a later listing.
For convenience, the implementation is included with the declaration of the class. In a real-world program, you would save the class declaration in String.hpp
and the implementation in String.cpp
. You would then add String.cpp
into your program (using add files or a make file) and have String.cpp #include String.hpp
.
Of course, in a real program, you’d use the C++ Standard Library String class, and not this string class in the first place.
Listing 16.2 describes an Employee
class that contains three string objects. These objects are used to hold an employee’s first and last names as well as their address.
Listing 16.2. The Employee
Class and Driver Program
Name: Edythe Levine.
Address: 1461 Shore Parkway.
Salary: 50000
Listing 16.2 shows the Employee
class, which contains three string objects (see lines 26–28): itsFirstName
, itsLastName
, and itsAddress
.
On line 71, an Employee
object called Edie
is created, and four values are passed in. On line 72, the Employee
access function SetSalary()
is called, with the constant value 50000
. Note that in a real program, this would be either a dynamic value (set at runtime) or a constant.
On line 73, a string called LastName
is created and initialized using a C++ string constant. This string object is then used as an argument to SetLastName()
on line 74.
On line 75, 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 (see line 18).
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 11 of Listing 16.1.
Looking at lines 78, 79, and 81, you see something that might appear unusual. You might be wondering why GetString()
has been tacked onto the different methods from the Employee
class:
78: cout << Edie.GetFirstName().GetString();
The Edie
object’s GetFirstName()
method returns a String
. Unfortunately, the String
object created in Listing 16.1 does not yet support the cout <<
operator. To satisfy cout
, you need to return a C-Style string. GetString()
is a method on your String
object that returns a C-Style string. This problem will be fixed soon.
A class that aggregates other objects does not have special access to those object’s member data and functions. Rather, it has whatever access is normally exposed. For example, Employee
objects do not have special access to the member variables of String
. If the Employee
object Edie
tries to access the private member variable itsLen
of its own itsFirstName
member variable, it receives a compile-time error. This is not much of a burden, however. The accessor functions provide an interface for the String
class, and the Employee
class need not worry about the implementation details, any more than it worries about how the integer variable, itsSalary
, stores its information.
Note
Aggregated members don’t have any special access to the members of the class within which they are aggregated. The only ability they have to access the instance that aggregates them is to have a copy of the owner class “this
” pointer passed to them at creation or at some point thereafter. If this is done, they have the same normal access to that object as they would to any other.
Note that the String
class provides an overloaded plus operator: 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 causes 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 just the return value is not sufficient to overload the function name; you must change the “constness” of the function itself.
When you have aggregated objects, there can be a cost in performance. Each time an Employee
string is constructed or copied, you are also constructing each of its aggregated string objects.
Uncommenting the cout
statements in Listing 16.1 reveals how often the constructors are called. Listing 16.3 rewrites the driver program to add print statements indicating where in the program objects are being created. Uncomment the lines in Listing 16.1, and then compile Listing 16.3.
Note
To compile this listing, uncomment lines 40, 53, 65, 77, 86, and 102 in Listing 16.1.
Listing 16.3. Aggregated Class Constructors
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 LastName...
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
Listing 16.3 uses the same class declarations as Listings 16.1 and 16.2. However, the cout
statements have been uncommented. The output from Listing 16.3 has been numbered to make analysis easier.
On line 71 of Listing 16.3, the statement Creating Edie...
is printed, as reflected on line 1 of the output. On line 72, an Employee
object, Edie
, is created with four parameters, the first three being strings. The output reflects the constructor for String
being called three times, as expected.
Line 74 prints an information statement, and then on line 75 is the statement Edie.SetFirstName("Edythe")
. This statement causes a temporary string to be created from the character string "Edythe"
, as reflected on lines 5 and 6 of the output. Note that the temporary String
object is destroyed immediately after it is used in the assignment statement.
On line 77, 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 8 of the output, but no destructor. This object is not destroyed until it goes out of scope at the end of the function.
On lines 81–87, the strings in the Employee
object are destroyed as the Employee
object falls out of scope, and the string LastName
, created on line 77, is destroyed as well when it falls out of scope.
Listing 16.3 illustrates how the creation of one Employee
object caused five string constructor calls. Listing 16.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 16.1 shows that ConstructorCount
is incremented each time a string constructor is called. The driver program in 16.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, leave in the lines that you uncommented in Listing 16.1 to run Listing 16.3, and in addition, uncomment lines 25, 41, 54, 66, 78, and 155 from Listing 16.1.
Listing 16.4. Passing by Value
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
The output shows that five string objects were created as part of creating one Employee
object on line 74. When the Employee
object is passed on line 82 to rPrintFunc()
by reference, no additional Employee
objects are created, and so no additional String
objects are created. (They, too, are passed by reference.) You can see this in the early part of the output where the constructure count remains at 5 and no constructures are called.
When, on line 85, the Employee
object is passed to PrintFunc()
by value, a copy of the Employee
is created, and three more string objects are created (by calls to the copy constructor).
At times, one class wants to draw on some of the capabilities of another class. For example, suppose 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 PartsList
class. This PartsList
is well tested and understood, and you’d like to build on that when making your PartsCatalog
, rather than inventing it from scratch.
You could create a new PartsCatalog
class and have it contain a PartsList
. The PartsCatalog
could delegate management of the linked list to its aggregated PartsList
object.
An alternative would be to make the PartsCatalog
derive from PartsList
and, thereby, inherit the properties of a PartsList
. Remembering, however, that public inheritance provides an is-a relationship, you should ask whether a PartsCatalog
really is a type of PartsList
.
One way to answer the question of whether PartsCatalog
is a PartsList
is to assume that PartsList
is the base and PartsCatalog
is the derived class, and then to ask these other questions:
1. Is anything in the base class that should not be in the derived? For example, does the PartsList
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 PartsLists
to do its job? If it might, you almost certainly want to use aggregation.
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 choose between public inheritance (the is-a relationship) and either private inheritance (explained later today) or aggregation (the has-a relationship).
• Aggregation—Declaring an object as a member of another class contained by that class. This is also referred to as has-a.
• Delegation—Using the members of an aggregated class to perform functions for the containing class.
• Implemented in terms of—Building one class on the capabilities of another without using public inheritance (for instance, by using protected or private inheritance).
Why not derive PartsCatalog
from PartsList
? The PartsCatalog
isn’t a PartsList
because PartsList
s 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 aggregation. The PartsCatalog
will delegate list management to an aggregated PartsList
. Listing 16.5 illustrates this approach.
Listing 16.5. Delegating to an Aggregated PartsList
(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
Note
Some compilers cannot compile line 264, even though it is legal C++. If your compiler complains about this line, change it to
264: void ShowAll() { thePartsList.Iterate(&Part::Display); }
(Note the addition of the ampersand in front of Part::Display
.) If this fixes the problem, immediately call your compiler vendor and complain.
Listing 16.5 reproduces the Part
, PartNode
, and PartsList
classes from Week 2 in Review.
A new class, PartsCatalog
, is declared on lines 257–267. PartsCatalog
has a PartsList
as its data member (line 265), 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. You can see that PartsList
is declared as a private member. The interface to this 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 269. The Part
that is passed in as a parameter is asked for the value of its itsPartNumber
member variable.
On line 274, this value is fed to the PartsList
’s Find()
method, and if no match is found, the number is inserted (line 276); otherwise, an informative error message is printed (starting on line 280).
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 aggregated PartsList
member of PartsCatalog
. No reason exists 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.
Note
If you want more information about PartsList
, review the Week 2 in Review listing and analysis!
If PartsCatalog
needed access to the protected members of PartsList
(in this case, none exist), or needed to override any of the PartsList
methods, then PartsCatalog
would be forced to inherit from PartsList
.
Because a PartsCatalog
is not a PartsList
object, and because you don’t want to expose the entire set of functionality of PartsList
to clients of PartsCatalog
, you would need to use private inheritance. Private inheritance allows you to inherit from another class and to keep the internals of that class completely private to your derived class.
The first thing to know about private inheritance is that all 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, only implementation.
To clients of the PartsCatalog
class, the PartsList
class is invisible. None of its interface is available to them: They can’t call any of its methods. They can call PartsCatalog
methods; however, PartsCatalog
methods can then access all of PartsList
because PartsCatalog
is derived from PartsList
. 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 aggregation. The private inheritance is just a convenience.
Listing 16.6 demonstrates the use of private inheritance by rewriting the PartsCatalog
class as privately derived from PartsList
.
Listing 16.6. Private Inheritance
(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
Listing 16.6 shows a changed interface to PartsCatalog
and the rewritten driver program. The interfaces to the other classes are unchanged from Listing 16.5.
On line 253 of Listing 16.6, PartsCatalog
is declared to derive privately from PartsList
. The interface to PartsCatalog
doesn’t change from Listing 16.5, although, of course, it no longer needs an object of type PartsList
as member data.
The PartsCatalog ShowAll()
function on line 160 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()
directly. Although PartsList
might allow other functions to be passed to Iterate()
, PartsCatalog
does not.
The Insert()
function on lines 164–284 has changed as well. Note, on line 269, that Find()
is now called directly because it is inherited from the base class. The call on line 271 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 can 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 enables the PartsCatalog
to inherit what it can use, but still provides mediated access to Insert()
and other methods to which client classes should not have direct access.
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.
After a class declares another to be its friend, all of the declaring classes’ member data and functions are public to the friend class. For example, if PartsNode
declares PartsList
to be a friend, all PartsNode
’s member data and functions are public as far as PartsList
is concerned.
It is important to note that friendship cannot be transferred. Although you are my friend and Joe is your friend, that doesn’t mean Joe is my friend. Friendship is not inherited, either. Again, although you are my friend and I’m willing to share my secrets with you, that 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. You might be willing to tell me your secrets, but that doesn’t mean I am willing to tell you mine.
To declare a class as a friend, you use the C++ friend keyword:
class ClassOne
{
public:
friend class BefriendedClass;
. . .
In this example, ClassOne
has declared BefriendedClass
as its friend. This means that BefriendedClass
now has full access to any of ClassOne
’s members.
Listing 16.7 illustrates friendship by rewriting the example from Listing 16.6, making PartsList
a friend of PartNode
. Note that this does not make PartNode
a friend of PartsList
.
Listing 16.7. Friend
Class Illustrated
(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
On line 79, the class PartsList
is declared to be a friend to the PartNode
class.
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 157, 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 a good enough reason does not exist 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, good reason might exist to use this declaration. But use it sparingly; it is often just as easy to use the public accessor methods, and doing so enables you to change one class without having to recompile the other.
You will often hear novice C++ programmers complain that friend declarations “undermine” the encapsulation so important to object-oriented programming. This is not necessarily true. The friend declaration makes the declared friend part of the class interface and does not have to undermine encapsulation. Use of a friend implies a commitment to parallel maintenance of both classes, which could reduce modularity.
class PartNode{
public:
friend class PartsList; // declares PartsList to be a friend of PartNode
};
You just learned that declaring another class a friend gives it total access. At times, you might 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, regardless of whether it is a member function of another class, to be a friend function.
Listing 16.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 enabled 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, “Working with Advanced Functions,” when you say cString + sString;
what you are really calling is cString.operator+(sString)
. Because 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 is converted to a string object by the appropriate constructor, and then operator+
is called using the two string objects. To clarify this, take a look at Listing 16.8.
Listing 16.8. Friendly operator+
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
The implementation of all the string methods except operator+
are unchanged from Listing 16.1. 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 143–154. 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 172, where operator+
is now called on a C-style string!
class PartNode
{ // ...
// make another class’s member function a _friend
friend void PartsList::Insert(Part *);
// make a global function a friend
friend int SomeFunction();
// ...
};
You are finally ready to give your String
class the capability to use cout
the same as 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 17, “Working with Streams,” presents the ins and outs (cin
s and cout
s?) of working with iostream
s; for now, Listing 16.9 illustrates how operator<<
can be overloaded using a friend function.
Listing 16.9. Overloading operator<<()
Hello world.
On lines 21–22, 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 157–161. 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 17.
Today, you saw how to delegate functionality to an aggregated object. You also saw how to implement one class in terms of another by using either aggregation or private inheritance. Aggregation is restricted in that the new class does not have access to the protected members of the aggregated class, and it cannot override the member functions of the aggregated object. Aggregation is simpler to use than inheritance, and should be used when possible.
You also saw how to declare both friend classes and friend functions. Using a friend function, you saw how to overload the extraction operator, to allow your new classes to use cout
the same as the built-in classes do.
Remember that public inheritance expresses is-a, aggregation expresses has-a, and private inheritance expresses implemented in terms of. The relationship delegates-to can be expressed using either aggregation or private inheritance, although aggregation is more common.
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 What is containment?
A Containment is another word for aggregation.
Q Why is aggregation 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. Aggregated classes hide their details; private inheritance exposes the implementation details. To some extent, this is also true for conventional public inheritance, which is sometimes used when aggregation would be a better solution.
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 idea 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 I 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 a friend for each form to which you want to grant this access.
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 be certain you understand the answers before going to tomorrow’s lesson.
1. How do you establish an is-a relationship?
2. How do you establish a has-a relationship?
3. What is the difference between aggregation 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?
1. Show the declaration of a class, Animal
, that contains a data member 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 16.1 to provide the String
class with an extraction operator (>>
).
5. BUG BUSTERS: What is wrong with this program?
6. Fix the listing in Exercise 5 so that it compiles.
7. BUG BUSTERS: What is wrong with this code?
8. Fix Exercise 7 so that it compiles.
18.218.224.226