Handling Related Types in C++

Suppose you're writing the employee-database program in C++. First, define a class called Employee that describes the common characteristics of all employees. For example:

class Employee

{

public:

Employee();

Employee( const char *nm );

char *getName() const;

private:

char name[30];

};

For simplicity, this Employee class stores only a name, though it could store many other characteristics as well, such as a birthdate, a social-security number, and an address.

Next, you can define a WageEmployee class that describes a particular type of employee: those who are paid by the hour. These employees have the characteristics common to all employees, plus some additional ones.

There are two ways you can use Employee when you define the WageEmployee class. One way is to give WageEmployee an Employee object as a data member. However, that doesn't properly describe the relationship between the two types. A wage-earning employee doesn't contain a generic employee; rather, a wage-earning employee is a special type of employee.

The second possibility is inheritance, which makes one class a special type of another. You can make WageEmployee inherit from Employee with the following syntax:

class WageEmployee : public Employee

{

public:

WageEmployee( const char *nm );

void setWage( float wg );

void setHours( float hrs );

private:

float wage;

float hours;

};

WageEmployee is a “derived class,” and Employee is its “base class.” To declare a derived class, you follow its name with a colon and the keyword public, followed by the name of its base class (you can also use the keyword private; this is described in the section “Public and Private Base Classes”). In the declaration of the derived class, you declare the members that are specific to it; that is, you describe the additional qualities that distinguish it from the base class.

Each instance of WageEmployee contains all of Employee's data members, in addition to its own. You can call any of Employee's or WageEmployee's member functions for a WageEmployee object. For example:

WageEmployee aWorker( "Bill Shapiro" );

char *str;

aWorker.setHours( 40.0 ); // call WageEmployee::setHours

str = aWorker.getName(); // call Employee::getname

Figure 7.1 illustrates the members contained in Employee and WageEmployee.

The member functions of a derived class do not have access to the private members of its base class. For example, the member functions of WageEmployee cannot access the private members of its base class Employee. For example, suppose you write the following function:

void WageEmployee::printName() const

{

cout << "Worker's name: "

<< name << '\n'; // Error: name is private

// member of Employee

}

Since name is one of the private members of the base class, it is inaccessible to any member function of WageEmployee.

This restriction may seem surprising. After all, if a WageEmployee is a kind of Employee, why shouldn't it have access to its own Employee characteristics? This restriction is designed to enforce encapsulation. If a derived class had access to its base class's private data, then anyone could access the private data of a class by simply deriving a new class from it. The point of making data private is to prevent programmers who use your class from writing code that depends on its implementation details, and this includes programmers who write derived classes. If the original class's implementation were changed, every class that derived from it would have to be rewritten as well.

Consequently, a derived class must use the base class's public interface, just like any other user of the class. You could rewrite the previous example as follows:

void WageEmployee::printName() const

{

cout << "Worker's name: "

<< getName() << '\n'; // Call Employee::getName

}

This function uses Employee's public interface to get the information it needs.

To make this C++ example more like the employee example in C, you can also define classes that describe salespersons and managers. Since salespersons are a kind of wage-earning employee, you can derive the SalesPerson class from the WageEmployee class.

class SalesPerson : public WageEmployee

{

public:

SalesPerson( const char *nm );

void setCommission( float comm );

void setSales( float sales );

private:

float commission;

float salesMade;

};

A SalesPerson object contains all the data members defined by Employee and WageEmployee, as well as the ones defined by SalesPerson. Similarly, you can call any of the member functions defined in these three classes for a SalesPerson object. (The Employee class is considered an “indirect” base class of SalesPerson, while the WageEmployee class is a “direct” base class of SalesPerson.)

Notice that this declaration means that WageEmployee is both a derived class and a base class. It derives from the Employee class and serves as the base for the SalesPerson class. You can define as many levels of inheritance as you want.

Managers are a type of employee who receive a fixed salary. Accordingly, you can derive the Manager class from Employee, as follows:

class Manager : public Employee

{

public:

Manager( const char *nm );

void setSalary( float salary );

private:

float weeklySalary;

};

The inheritance relationships among all of these classes are shown in Figure 7.2. This figure illustrates a “class hierarchy,” or a group of user-defined types organized according to their relationship to one another. The class at the top represents the most general type, and the classes at the bottom represent the more specialized types. As you'll learn in Part 3, “Object-Oriented Design,” designing an appropriate class hierarchy is one of the most important steps in writing an object-oriented program.

Notice that Employee acts as a base class for more than one class (WageEmployee and Manager). Any number of derived classes can inherit from a given base class.

Also notice that the Manager class shares members only with Employee. It doesn't have any of the members defined by WageEmployee or SalesPerson.