13. Class Inheritance

In this chapter you’ll learn about the following:

• Inheritance as an is-a relationship

• How to publicly derive one class from another

• Protected access

• Constructor member initializer lists

• Upcasting and downcasting

• Virtual member functions

• Early (static) binding and late (dynamic) binding

• Abstract base classes

• Pure virtual functions

• When and how to use public inheritance

One of the main goals of object-oriented programming is to provide reusable code. When you develop a new project, particularly if the project is large, it’s nice to be able to reuse proven code rather than to reinvent it. Employing old code saves time and because it has already been used and tested, can help suppress the introduction of bugs into a program. Also the less you have to concern yourself with details, the better you can concentrate on overall program strategy.

Traditional C function libraries provide reusability through predefined, precompiled functions, such as strlen() and rand(), that you can use in your programs. Many vendors furnish specialized C libraries that provide functions beyond those of the standard C library. For example, you can purchase libraries of database management functions and of screen control functions. However, function libraries have a limitation: Unless the vendor supplies the source code for its library functions (and often it doesn’t), you can’t extend or modify the functions to meet your particular needs. Instead, you have to shape your program to meet the workings of the library. Even if the vendor does supply the source code, you run the risk of unintentionally modifying how part of a function works or of altering the relationships among library functions as you add your changes.

C++ classes bring a higher level of reusability. Many vendors now offer class libraries, which consist of class declarations and implementations. Because a class combines data representation with class methods, it provides a more integrated package than does a function library. A single class, for example, may provide all the resources for managing a dialog box. Often class libraries are available in source code, which means you can modify them to meet your needs. But C++ has a better method than code modification for extending and modifying classes. This method, called class inheritance, lets you derive new classes from old ones, with the derived class inheriting the properties, including the methods, of the old class, called a base class. Just as inheriting a fortune is usually easier than earning one from scratch, deriving a class through inheritance is usually easier than designing a new one. Here are some things you can do with inheritance:

• You can add functionality to an existing class. For example, given a basic array class, you could add arithmetic operations.

• You can add to the data that a class represents. For example, given a basic string class, you could derive a class that adds a data member representing a color to be used when displaying the string.

• You can modify how a class method behaves. For example, given a Passenger class that represents the services provided to an airline passenger, you can derive a FirstClassPassenger class that provides a higher level of services.

Of course, you could accomplish the same aims by duplicating the original class code and modifying it, but the inheritance mechanism allows you to proceed by just providing the new features. You don’t even need access to the source code to derive a class. Thus, if you purchase a class library that provides only the header files and the compiled code for class methods, you can still derive new classes based on the library classes. Conversely, you can distribute your own classes to others, keeping parts of your implementation secret, yet still giving your clients the option of adding features to your classes.

Inheritance is a splendid concept, and its basic implementation is quite simple. But managing inheritance so that it works properly in all situations requires some adjustments. This chapter looks at both the simple and the subtle aspects of inheritance.

Beginning with a Simple Base Class

When one class inherits from another, the original class is called a base class, and the inheriting class is called a derived class. So to illustrate inheritance, let’s begin with a base class. The Webtown Social Club has decided to keep track of its members who play table tennis. As head programmer for the club, you have designed the simple TableTennisPlayer class defined in Listings 13.1 and 13.2.

Listing 13.1. tabtenn0.h


// tabtenn0.h -- a table-tennis base class
#ifndef TABTENN0_H_
#define TABTENN0_H_
#include <string>
using std::string;
// simple base class
class TableTennisPlayer
{
private:
    string firstname;
    string lastname;
    bool hasTable;
public:
    TableTennisPlayer (const string & fn = "none",
                       const string & ln = "none", bool ht = false);
    void Name() const;
    bool HasTable() const { return hasTable; };
    void ResetTable(bool v) { hasTable = v; };
};
#endif


Listing 13.2. tabtenn0.cpp


//tabtenn0.cpp -- simple base-class methods
#include "tabtenn0.h"
#include <iostream>

TableTennisPlayer::TableTennisPlayer (const string & fn,
    const string & ln, bool ht) : firstname(fn),
           lastname(ln), hasTable(ht) {}

void TableTennisPlayer::Name() const
{
    std::cout << lastname << ", " << firstname;
}


All the TableTennisPlayer class does is keep track of the players’ names and whether they have tables. There are a couple of points to notice. First, the class uses the standard string class to hold the names. This is more convenient, flexible, and safer than using a character array. And it is rather more professional than the String class of Chapter 12, “Classes and Dynamic Memory Allocation.” Second, the constructor uses the member initializer list syntax introduced in Chapter 12. You could also do this:

TableTennisPlayer::TableTennisPlayer (const string & fn,
                   const string & ln, bool ht)
{
    firstname = fn;
    lastname = ln;
    hasTable = ht;
}

However, this approach has the effect of first calling the default string constructor for firstname and then invoking the string assignment operator to reset firstname to fn. But the member initializer list syntax saves a step by just using the string copy constructor to initialize firstname to fn.

Listing 13.3 shows this modest class in action.

Listing 13.3. usett0.cpp


// usett0.cpp -- using a base class
#include <iostream>
#include "tabtenn0.h"

int main ( void )
{
    using std::cout;
    TableTennisPlayer player1("Chuck", "Blizzard", true);
    TableTennisPlayer player2("Tara", "Boomdea", false);
    player1.Name();
    if (player1.HasTable())
        cout << ": has a table. ";
    else
        cout << ": hasn't a table. ";
    player2.Name();
    if (player2.HasTable())
        cout << ": has a table";
    else
        cout << ": hasn't a table. ";

    return 0;
}


And here’s the output of the program in Listings 13.1, 13.2, and 13.3:

Blizzard, Chuck: has a table.
Boomdea, Tara: hasn't a table.

Note that the program uses constructors with C-style string arguments:

TableTennisPlayer player1("Chuck", "Blizzard", true);
TableTennisPlayer player2("Tara", "Boomdea", false);

But the formal parameters for the constructor were declared as type const string &. This is a type mismatch, but the string class, much like the String class of Chapter 12, has a constructor with a const char * parameter, and that constructor is used automatically to create a string object initialized by the C-style string. In short, you can use either a string object or a C-style string as an argument to the TableTennisPlayer constructor. The first invokes a string constructor with a const string & parameter, and the second invokes a string constructor with a const char * parameter.

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

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