Special Relationships Between Derived and Base Classes

A derived class has some special relationships with the base class. One, which you’ve just seen, is that a derived-class object can use base-class methods, provided that the methods are not private:

RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
rplayer1.Name();  // derived object uses base method

Two other important relationships are that a base-class pointer can point to a derived-class object without an explicit type cast and that a base-class reference can refer to a derived-class object without an explicit type cast:

RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
TableTennisPlayer & rt = rplayer;
TableTennisPlayer * pt = &rplayer;
rt.Name();   // invoke Name() with reference
pt->Name();  // invoke Name() with pointer

However, a base-class pointer or reference can invoke just base-class methods, so you couldn’t use rt or pt to invoke, say, the derived-class ResetRanking() method.

Ordinarily, C++ requires that references and pointer types match the assigned types, but this rule is relaxed for inheritance. However, the rule relaxation is just in one direction. You can’t assign base-class objects and addresses to derived-class references and pointers:

TableTennisPlayer player("Betsy", "Bloop", true);
RatedPlayer & rr = player;      // NOT ALLOWED
RatedPlayer * pr = player;      // NOT ALLOWED

Both these sets of rules make sense. For example, consider the implications of having a base-class reference refer to a derived object. In this case, you can use the base-class reference to invoke base-class methods for the derived-class object. Because the derived class inherits the base-class methods and data members, this causes no problems. Now consider what would happen if you could assign a base-class object to a derived-class reference. The derived-class reference would be able to invoke derived-class methods for the base object, and that could cause problems. For example, applying the RatedPlayer::Rating() method to a TableTennisPlayer object makes no sense because the TableTennisPlayer object doesn’t have a rating member.

The fact that base-class references and pointers can refer to derived-class objects has some interesting consequences. One is that functions defined with base-class reference or pointer arguments can be used with either base-class or derived-class objects. For instance, consider this function:

void Show(const TableTennisPlayer & rt)
{
    using std::cout;
    cout << "Name: ";
    rt.Name();
    cout << " Table: ";
    if (rt.HasTable())
        cout << "yes ";
    else
        cout << "no ";
}

The formal parameter rt is a reference to a base class, so it can refer to a base-class object or to a derived-class object. Thus, you can use Show() with either a TableTennis argument or a RatedPlayer argument:

TableTennisPlayer player1("Tara", "Boomdea", false);
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
Show(player1);   // works with TableTennisPlayer argument
Show(rplayer1);  // works with RatedPlayer argument

A similar relationship would hold for a function with a pointer-to-base-class formal parameter; it could be used with either the address of a base-class object or the address of a derived-class object as an actual argument:

void Wohs(const TableTennisPlayer * pt);  // function with pointer parameter
...
TableTennisPlayer player1("Tara", "Boomdea", false);
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
Wohs(&player1);   // works with TableTennisPlayer * argument
Wohs(&rplayer1);  // works with RatedPlayer * argument

The reference compatibility property also allows you to initialize a base-class object to a derived-class object, although somewhat indirectly. Suppose you have this code:

RatedPlayer olaf1(1840, "Olaf", "Loaf", true);
TableTennisPlayer olaf2(olaf1);

The exact match for initializing olaf2 would be a constructor with this prototype:

TableTennisPlayer(const RatedPlayer &);       // doesn't exist

The class definitions don’t include this constructor, but there is the implicit copy constructor:

// implicit copy constructor
TableTennisPlayer(const TableTennisPlayer &);

The formal parameter is a reference to the base type, so it can refer to a derived type. Thus, the attempt to initialize olaf2 to olaf1 uses this constructor, which copies the firstname, lastname, and hasTable members. In other words, it initializes olaf2 to the TableTennisPlayer object embedded in the RatedPlayer object olaf1.

Similarly, you can assign a derived-class object to a base-class object:

RatedPlayer olaf1(1840, "Olaf", "Loaf", true);
TableTennisPlayer winner;
winner = olaf1; // assign derived to base object

In this case, the program uses the implicit overloaded assignment operator:

TableTennisPlayer & operator=(const TableTennisPlayer &) const;

Again, a base-class reference refers to a derived-class object, and just the base-class portion of olaf1 is copied to winner.

Inheritance: An Is-a Relationship

The special relationship between a derived class and a base class is based on an underlying model for C++ inheritance. Actually, C++ has three varieties of inheritance: public, protected, and private. Public inheritance is the most common form, and it models an is-a relationship. This is shorthand for saying that an object of a derived class should also be an object of the base class. Anything you do with a base-class object, you should be able to do with a derived-class object. Suppose, for example, that you have a Fruit class. It could store, say, the weight and caloric content of a fruit. Because a banana is a particular kind of fruit, you could derive a Banana class from the Fruit class. The new class would inherit all the data members of the original class, so a Banana object would have members representing the weight and caloric content of a banana. The new Banana class also might add members that apply particularly to bananas and not to fruit in general, such as the Banana Institute Peel Index. Because the derived class can add features, it’s probably more accurate to describe the relationship as an is-a-kind-of relationship, but is-a is the usual term.

To clarify is-a relationships, let’s look at some examples that don’t match that model. Public inheritance doesn’t model a has-a relationship. A lunch, for example, might contain a fruit. But a lunch, in general, is not a fruit. Therefore, you should not derive a Lunch class from the Fruit class in an attempt to place fruit in a lunch. The correct way to handle putting fruit into a lunch is to consider the matter as a has-a relationship: A lunch has a fruit. As you’ll see in Chapter 14, that’s most easily modeled by including a Fruit object as a data member of a Lunch class (see Figure 13.3).

Figure 13.3. Is-a and has-a relationships.

Image

Public inheritance doesn’t model an is-like-a relationship—that is, it doesn’t do similes. It’s often pointed out that lawyers are like sharks. But it is not literally true that a lawyer is a shark. For example, sharks can live underwater. Therefore, you shouldn’t derive a Lawyer class from a Shark class. Inheritance can add properties to a base class; it doesn’t remove properties from a base class. In some cases, shared characteristics can be handled by designing a class encompassing those characteristics and then using that class, either in an is-a or has-a relationship, to define the related classes.

Public inheritance doesn’t model an is-implemented-as-a relationship. For example, you could implement a stack by using an array. However, it wouldn’t be proper to derive a Stack class from an Array class. A stack is not an array. For example, array indexing is not a stack property. Also a stack could be implemented in some other way, such as by using a linked list. A proper approach would be to hide the array implementation by giving the stack a private Array object member.

Public inheritance doesn’t model a uses-a relationship. For example, a computer can use a laser printer, but it doesn’t make sense to derive a Printer class from a Computer class, or vice versa. You might, however, devise friend functions or classes to handle communication between Printer objects and Computer objects.

Nothing in the C++ language prevents you from using public inheritance to model has-a, is-implemented-as-a, or uses-a relationships. However, doing so usually leads to programming problems. So let’s stick to the is-a relationships.

Polymorphic Public Inheritance

The RatedPlayer example of inheritance is a simple one. Objects of the derived class use the base-class methods without change. But you can encounter situations in which you want a method to behave differently for the derived class than it does for the base class. That is, the way a particular method behaves may depend on the object that invokes it. This more sophisticated behavior is termed polymorphic (“having many forms”) because you can have multiple behaviors for a method, depending on the context. There are two key mechanisms for implementing polymorphic public inheritance:

• Redefining base-class methods in a derived class

• Using virtual methods

It’s time for another example. You have leveraged your experience with the Webtown Social Club to become head programmer for the Pontoon National Bank. The first thing the bank asks you to do is develop two classes. One class will represent its basic checking plan, the Brass Account, and the second class will represent the Brass Plus checking account, which adds an overdraft protection feature. That is, if a user writes a check larger (but not too much larger) than his or her balance, the bank will cover the check, charging the user for the excess payment and adding a surcharge. You can characterize the two accounts in terms of data to be stored and operations to be allowed.

First, here is the information for a Brass Account checking plan:

• Client name

• Account number

• Current balance

And here are the operations to be represented:

• Creating an account

• Depositing money into the account

Withdrawing money from the account

• Displaying the account information

For the Brass Plus Account checking plan, the Pontoon National Bank wants all the features of the Brass Account as well as the following additional items of information:

• An upper limit to the overdraft protection

• An interest rate charged for overdraft loans

• The overdraft amount currently owed to the bank

No additional operations are needed, but two operations need to be implemented differently:

• The withdrawing money operation has to incorporate overdraft protection for the Brass Plus Account

• The display operation has to show the additional information required by the Brass Plus Account

Suppose you call one class Brass and the second class BrassPlus. Should you derive BrassPlus publicly from Brass? To answer this question, first answer another: Does the BrassPlus class meet the is-a test? Sure. Everything that is true of a Brass object will be true for a BrassPlus object. Both store a client name, an account number, and a balance. With both, you can make deposits and withdrawals and display account information. Note that the is-a relationship is not, in general, symmetric. A fruit, in general, is not a banana; similarly, a Brass object won’t have all the capabilities of a BrassPlus object.

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

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