Day 16. Advanced Class Relationship

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

Aggregation

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

Image

Image

Image

Image

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.

Image

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

Image

Image

Image

Image


Name: Edythe Levine.
Address: 1461 Shore Parkway.
Salary: 50000

Image

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.

Accessing Members of the Aggregated Class

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.

Controlling Access to Aggregated Members

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.

Cost of Aggregation

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

Image

Image

Image

Image


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

Image

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.

Copying by Value

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

Image

Image

Image

Image


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

Image

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

Implementation in Terms of Inheritance Versus Aggregation/Delegation

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

Terminology

Several terms are being used here. The following helps to summarize these key terms:

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

Using Delegation

Why not derive PartsCatalog from PartsList? The PartsCatalog isn’t a PartsList because PartsLists 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

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image


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

Image

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!

Private Inheritance

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

Image

Image

Image

Image

Image

Image

Image

Image


(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

Image

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.

Image

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

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

Image

Image

Image

Image

Image

Image

Image

Image


(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

Image

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.

Note

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.

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 PartsList;  // declares PartsList to be a friend of PartNode
};

Friend Functions

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.

Friend Functions and Operator Overloading

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+

Image

Image

Image

Image

Image


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

Image

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!

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 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 (cins and couts?) of working with iostreams; for now, Listing 16.9 illustrates how operator<< can be overloaded using a friend function.

Listing 16.9. Overloading operator<<()

Image

Image

Image

Image

Image

Image


Hello world.

Image

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.

Summary

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

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 be certain you understand the answers before going to tomorrow’s lesson.

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 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?

Exercises

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?

Image

6. Fix the listing in Exercise 5 so that it compiles.

7. BUG BUSTERS: What is wrong with this code?

Image

8. Fix Exercise 7 so that it compiles.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.44.182