In this section, we use an inheritance hierarchy containing types of employees in a company’s payroll application to discuss the relationship between a base class and a derived class. Commission employees (who will be represented as objects of a base class) are paid a percentage of their sales, while base-salaried commission employees (who will be represented as objects of a derived class) receive a base salary plus a percentage of their sales. We divide our discussion of the relationship between commission employees and base-salaried commission employees into a carefully paced series of examples.
CommissionEmployee
ClassLet’s examine CommissionEmployee
’s class definition (Figs. 11.4–11.5). The CommissionEmployee
header (Fig. 11.4) specifies class CommissionEmployee
’s public
services, which include a constructor (lines 10–11) and member functions earnings
(line 28) and toString
(line 29). Lines 13–26 declare public
get and set functions that manipulate the class’s data members (declared in lines 31–35) firstName
, lastName
, socialSecurityNumber
, grossSales
and commissionRate
. Member functions setGrossSales
(defined in lines 46–52 of Fig. 11.5) and setCommissionRate
(defined in lines 58–64 of Fig. 11.5), for example, validate their arguments before assigning the values to data members grossSales
and commissionRate
, respectively.
CommissionEmployee
ConstructorThe CommissionEmployee
constructor definition purposely does not use member-initializer syntax in the first several examples of this section. This will enable us to demonstrate how private
and protected
specifiers affect member access in derived classes. As shown in Fig. 11.5, lines 12–14, we assign values to data members firstName
, lastName
and socialSecurityNumber
in the constructor body. Later in this section, we’ll return to using member-initializer lists in the constructors.
We do not validate the values of the constructor’s arguments first
, last
and ssn
before assigning them to the corresponding data members. We certainly could validate the first and last names—perhaps by ensuring that they’re of a reasonable length. Similarly, a social security number could be validated to ensure that it contains nine digits, with or without dashes (e.g., 123-45-6789
or 123456789
).
CommissionEmployee
Member Functions earnings
and toString
Member function earnings
(lines 72–74) calculates a CommissionEmployee
’s earnings. Line 73 multiplies the commissionRate
by the grossSales
and returns the result. Member function toString
(lines 77–85) displays the values of a CommissionEmployee
object’s data members.
CommissionEmployee
Figure 11.6 tests class CommissionEmployee
. Line 10 instantiates CommissionEmployee
object employee
and invokes the constructor to initialize the object with "Sue"
as the first name, "Jones"
as the last name, "222-22-2222"
as the social security number, 10000
as the gross sales amount and .06
as the commission rate. Lines 14–20 use employee
’s get functions to display the values of its data members. Lines 22–23 invoke the object’s member functions setGrossSales
and setCommissionRate
to change the values of data members grossSales
and commissionRate
, respectively. Lines 24–25 then call employee
’s toString
member function to get and output the updated CommissionEmployee
information. Finally, line 28 displays the CommissionEmployee
’s earnings, calculated by the object’s earnings
member function using the updated values of data members grossSales
and commissionRate
.
BasePlusCommissionEmployee
Class Without Using InheritanceWe now discuss the second part of our introduction to inheritance by creating and testing a completely new and independent class BasePlusCommissionEmployee (Figs. 11.7–11.8), which contains a first name, last name, social security number, gross sales amount, commission rate and base salary.
BasePlusCommissionEmployee
The BasePlusCommissionEmployee
header (Fig. 11.7) specifies class BasePlusCommissionEmployee
’s public
services, which include the BasePlusCommissionEmployee
constructor (lines 11–12) and member functions earnings
(line 32) and toString
(line 33). Lines 14–30 declare public
get and set functions for the class’s private
data members (declared in lines 35–40) firstName
, lastName
, socialSecurityNumber
, grossSales
, commissionRate
and baseSalary
. These variables and member functions encapsulate all the necessary features of a base-salaried commission employee. Note the similarity between this class and class CommissionEmployee
(Figs. 11.4–11.5)—in this example, we do not yet exploit that similarity.
Class BasePlusCommissionEmployee
’s earnings
member function (defined in lines 93–95 of Fig. 11.8) computes the earnings of a base-salaried commission employee. Line 94 returns the result of adding the employee’s base salary to the product of the commission rate and the employee’s gross sales.
BasePlusCommissionEmployee
Figure 11.9 tests class BasePlusCommissionEmployee
. Lines 10–11 instantiate object employee
of class BasePlusCommissionEmployee
, passing "Bob"
, "Lewis"
, "333-33-3333"
, 5000
, .04
and 300
to the constructor as the first name, last name, social security number, gross sales, commission rate and base salary, respectively. Lines 15–22 use BasePlusCommissionEmployee
’s get functions to retrieve the values of the object’s data members for output. Line 23 invokes the object’s setBaseSalary
member function to change the base salary. Member function setBaseSalary
(Fig. 11.8, lines 79–85) ensures that data member baseSalary
is not assigned a negative value, because an employee’s base salary cannot be negative. Lines 24–25 of Fig. 11.9 invoke the object’s toString
member function to get the updated BasePlusCommissionEmployee
’s information, and line 28 calls member function earnings
to display the BasePlusCommissionEmployee
’s earnings.
BasePlusCommissionEmployee
and Class CommissionEmployee
Most of the code for class BasePlusCommissionEmployee
(Figs. 11.7–11.8) is similar, if not identical, to the code for class CommissionEmployee
(Figs. 11.4–11.5). For example, in class BasePlusCommissionEmployee
, private
data members firstName
and lastName
and member functions setFirstName
, getFirstName
, setLastName
and getLastName
are identical to those of class CommissionEmployee
. Classes CommissionEmployee
and BasePlusCommissionEmployee
also both contain private
data members socialSecurityNumber
, commissionRate
and grossSales
, as well as get and set functions to manipulate these members. In addition, the BasePlusCommissionEmployee
constructor is almost identical to that of class CommissionEmployee
, except that BasePlusCommissionEmployee
’s constructor also sets the baseSalary
. The other additions to class BasePlusCommissionEmployee
are private
data member baseSalary
and member functions setBaseSalary
and getBaseSalary
. Class BasePlusCommissionEmployee
’s toString
member function is nearly identical to that of class CommissionEmployee
, except that BasePlusCommissionEmployee
’s toString
also outputs the value of data member baseSalary
.
We literally copied code from class CommissionEmployee
and pasted it into class BasePlusCommissionEmployee
, then modified class BasePlusCommissionEmployee
to include a base salary and member functions that manipulate the base salary. This copy-and-paste approach is error prone and time consuming.
Copying and pasting code from one class to another can spread many physical copies of the same code and can spread errors throughout a system, creating a code-maintenance nightmare. To avoid duplicating code (and possibly errors), use inheritance, rather than the “copy-and-paste” approach, in situations where you want one class to “absorb” the data members and member functions of another class.
With inheritance, the common data members and member functions of all the classes in the hierarchy are declared in a base class. When changes are required for these common features, you need to make the changes only in the base class—derived classes then inherit the changes. Without inheritance, changes would need to be made to all the source-code files that contain a copy of the code in question.
CommissionEmployee–BasePlusCommissionEmployee
Inheritance HierarchyNow we create and test a new BasePlusCommissionEmployee
class (Figs. 11.10–11.11) that derives from class CommissionEmployee
(Figs. 11.4–11.5). In this example, a BasePlusCommissionEmployee
object is a CommissionEmployee
(because inheritance passes on the capabilities of class CommissionEmployee
), but class BasePlusCommissionEmployee
also has data member baseSalary
(Fig. 11.10, line 21). The colon (:
) in line 10 of the class definition indicates inheritance. Keyword public
indicates the type of inheritance. As a derived class (formed with public
inheritance), BasePlusCommissionEmployee
inherits all the members of class CommissionEmployee
, except for the constructor—each class provides its own constructors that are specific to the class. (Destructors, too, are not inherited.) Thus, the public
services of BasePlusCommissionEmployee
include its constructor (lines 12–13) and the public
member functions inherited from class CommissionEmployee
—although we cannot see these inherited member functions in BasePlusCommissionEmployee
’s source code, they’re nevertheless a part of derived-class BasePlusCommissionEmployee
. The derived class’s public
services also include member functions setBaseSalary
, getBaseSalary
, earnings
and toString
(lines 15–19).
Figure 11.11 shows BasePlusCommissionEmployee
’s member-function implementations. The constructor (lines 10–16) introduces base-class initializer syntax (line 14), which uses a member initializer to pass arguments to the base-class (CommissionEmployee
)
constructor. C++ requires that a derived-class constructor call its base-class constructor to initialize the base-class data members that are inherited into the derived class. Line 14 does this by explicitly invoking the CommissionEmployee
constructor by name, passing the constructor’s parameters first
, last
, ssn
, sales
and rate
as arguments to initialize the base-class data members firstName
, lastName
, socialSecurityNumber
, grossSales
and commissionRate
, respectively. The compiler would issue an error if BasePlusCommissionEmployee
’s constructor did not invoke class CommissionEmployee
’s constructor explicitly—in this case, C++ attempts to invoke class CommissionEmployee
’s default constructor implicitly, but the class does not have such a constructor. Recall from Chapter 3 that the compiler provides a default constructor with no parameters in any class that does not explicitly include a constructor. However, CommissionEmployee
does explicitly include a constructor, so a default constructor is not provided.
When a derived-class constructor calls a base-class constructor, the arguments passed to the base-class constructor must be consistent with the number and types of parameters specified in one of the base-class constructors; otherwise, a compilation error occurs.
In a derived-class constructor, invoking base-class constructors and initializing member objects explicitly in the member initializer list prevents duplicate initialization in which a default constructor is called, then data members are modified again in the derived-class constructor’s body.
private
MembersThe compiler generates errors for line 35 of Fig. 11.11 because base-class CommissionEmployee
’s data members commissionRate
and grossSales
are private
—derived-class BasePlusCommissionEmployee
’s member functions are not allowed to access base-class CommissionEmployee
’s private
data. The compiler issues additional errors in lines 44–47 of BasePlusCommissionEmployee
’s toString
member function for the same reason. As you can see, C++ rigidly enforces restrictions on accessing private
data members, so that even a derived class (which is intimately related to its base class) cannot access the base class’s private
data.
BasePlusCommissionEmployee
We purposely included the erroneous code in Fig. 11.11 to emphasize that a derived class’s member functions cannot access its base class’s private
data. The errors in BasePlusCommissionEmployee
could have been prevented by using the get member functions inherited from class CommissionEmployee
. For example, line 35 could have invoked getCommissionRate
and getGrossSales
to access CommissionEmployee
’s private
data members commissionRate
and grossSales
, respectively. Similarly, lines 44–47 could have used appropriate get member functions to retrieve the values of the base class’s data members. In the next example, we show how using protected
data also allows us to avoid the errors encountered in this example.
#include
Notice that we #include
the base class’s header in the derived class’s header (line 8 of Fig. 11.10). This is necessary for three reasons. First, for the derived class to use the base class’s name in line 10, we must tell the compiler that the base class exists—the class definition in CommissionEmployee.h
does exactly that.
The second reason is that the compiler uses a class definition to determine the size of an object of that class (as we discussed in Section 9.3). A client program that creates an object of a class #include
s the class definition to enable the compiler to reserve the proper amount of memory for the object. When using inheritance, a derived-class object’s size depends on the data members declared explicitly in its class definition and the data members inherited from its direct and indirect base classes. Including the base class’s definition in line 8 of Fig. 11.10 allows the compiler to determine the memory requirements for the base class’s data members that become part of a derived-class object and thus contribute to its total size.
The last reason for line 8 is to allow the compiler to determine whether the derived class uses the base class’s inherited members properly. For example, in the program of Figs. 11.10–11.11, the compiler uses the base-class header to determine that the data members being accessed by the derived class are private
in the base class. Since these are inaccessible to the derived class, the compiler generates errors. The compiler also uses the base class’s function prototypes to validate function calls made by the derived class to the inherited base-class functions.
In Section 9.3, we discussed the linking process for creating an executable Time
application. In that example, you saw that the client’s object code was linked with the object code for class Time
, as well as the object code for any C++ Standard Library classes used either in the client code or in class Time
.
The linking process is similar for a program that uses classes in an inheritance hierarchy. The process requires the object code for all classes used in the program and the object code for the direct and indirect base classes of any derived classes used by the program. Suppose a client wants to create an application that uses class BasePlusCommissionEmployee
, which is a derived class of CommissionEmployee
(we’ll see an example of this in Section 11.3.4). When compiling the client application, the client’s object code must be linked with the object code for classes BasePlusCommissionEmployee
and CommissionEmployee
, because BasePlusCommissionEmployee
inherits member functions from its base-class CommissionEmployee
. The code is also linked with the object code for any C++ Standard Library classes used in class CommissionEmployee
, class BasePlusCommissionEmployee
or the client code. This provides the program with access to the implementations of all of the functionality that the program may use.
CommissionEmployee–BasePlusCommissionEmployee
Inheritance Hierarchy Using protected
DataChapter 3 introduced access specifiers public
and private
. A base class’s public
members are accessible within its body and anywhere that the program has a handle (i.e., a name, reference or pointer) to an object of that class or one of its derived classes, including in derived classes. A base class’s private
members are accessible only within its body and to the friend
s of that base class. In this section, we introduce the access specifier protected
.
Using protected
access offers an intermediate level of protection between public
and private
access. To enable class BasePlusCommissionEmployee
to directly access CommissionEmployee
data members firstName
, lastName
, socialSecurityNumber
, grossSales
and commissionRate
, we can declare those members as protected
in the base class. A base class’s protected
members can be accessed within the body of that base class, by members and friend
s of that base class, and by members and friend
s of any classes derived from that base class.
CommissionEmployee
with protected
DataClass CommissionEmployee
(Fig. 11.12) now declares data members firstName
, lastName
, socialSecurityNumber
, grossSales
and commissionRate
as protected
(lines 30–35) rather than private
. The member-function implementations are identical to those in Fig. 11.5, so CommissionEmployee.cpp
is not shown here.
BasePlusCommissionEmployee
The definition of class BasePlusCommissionEmployee
from Figs. 11.10–11.11 remains unchanged, so we do not show it again here. Now that BasePlusCommissionEmployee
inherits from the updated class CommissionEmployee
(Fig. 11.12), BasePlusCommissionEmployee
objects can access inherited data members that are declared protected
in class CommissionEmployee
(i.e., data members firstName
, lastName
, socialSecurityNumber
, grossSales
and commissionRate
). As a result, the compiler does not generate errors when compiling the BasePlusCommissionEmployee
earnings
and toString
member-function definitions in Fig. 11.11 (lines 33–36 and 39–50, respectively). This shows the special privileges that a derived class is granted to access protected
base-class data members. Objects of a derived class also can access protected
members in any of that derived class’s indirect base classes.
Class BasePlusCommissionEmployee
does not inherit class CommissionEmployee
’s constructor. However, class BasePlusCommissionEmployee
’s constructor (Fig. 11.11, lines 10–16) calls class CommissionEmployee
’s constructor explicitly with member-initializer syntax (line 14). Recall that BasePlusCommissionEmployee
’s constructor must explicitly call the constructor of class CommissionEmployee
, because CommissionEmployee
does not contain a default constructor that could be invoked implicitly.
BasePlusCommissionEmployee
ClassTo test the updated class hierarchy, we reused the test program from Fig. 11.9. As shown in Fig. 11.13, the output is identical to that of Fig. 11.9.
We created the first class BasePlusCommissionEmployee
without using inheritance and created this version of BasePlusCommissionEmployee
using inheritance; however, both classes provide the same functionality. The code for derived-class BasePlusCommissionEmployee
(i.e., the header and implementation files) is considerably shorter than the code for the noninherited version of the class, because the inherited version absorbs much of its functionality from CommissionEmployee
, whereas the noninherited version does not absorb any functionality. Also, there is now only one copy of the CommissionEmployee
functionality declared and defined in class CommissionEmployee
. This makes the source code easier to maintain, modify and debug, because the source code related to a CommissionEmployee
exists only in the files CommissionEmployee.h
and CommissionEmployee.cpp
.
protected
DataIn this example, we declared base-class data members as protected
, so derived classes can modify the data directly. Inheriting protected
data members slightly improves performance, because we can directly access the members without incurring the overhead of calls to set or get member functions.
In most cases, it’s better to use private
data members to encourage proper software engineering, and leave code optimization issues to the compiler. Your code will be easier to maintain, modify and debug.
Using protected
data members creates two serious problems. First, the derived-class object does not have to use a member function to set the value of the base class’s protected
data member. An invalid value can easily be assigned to the protected
data member, thus leaving the object in an inconsistent state—e.g., with CommissionEmployee
’s data member grossSales
declared as protected
, a derived-class object can assign a negative value to grossSales
. The second problem with using protected
data members is that derived-class member functions are more likely to be written so that they depend on the base-class implementation. Derived classes should depend only on the base-class services (i.e., non-private
member functions) and not on the base-class implementation. With protected
data members in the base class, if the base-class implementation changes, we may need to modify all derived classes of that base class. For example, if for some reason we were to change the names of data members firstName
and lastName
to first
and last
, then we’d have to do so for all occurrences in which a derived class references these base-class data members directly. Such software is said to be fragile or brittle, because a small change in the base class can “break” the derived-class implementation. You should be able to change the base-class implementation while still providing the same services to derived classes. Of course, if the base-class services change, we must reimplement our derived classes—good object-oriented design attempts to prevent this.
It’s appropriate to use the protected
access specifier when a base class should provide a service (i.e., a non-private
member function) only to its derived classes and friend
s.
Declaring base-class data members private
(as opposed to declaring them protected
) enables you to change the base-class implementation without having to change derived-class implementations.
CommissionEmployee–BasePlusCommissionEmployee
Inheritance Hierarchy Using private
DataWe now reexamine our hierarchy once more, this time using best software engineering practices. Class CommissionEmployee
now declares data members firstName
, lastName
, socialSecurityNumber
, grossSales
and commissionRate
as private
, as shown previously in lines 31–35 of Fig. 11.4.
CommissionEmployee
’s Member-Function DefinitionsIn the CommissionEmployee
constructor implementation (Fig. 11.14, lines 10–15), we use member initializers (line 12) to set the values of the members firstName
, lastName
and socialSecurityNumber
. Though we do not do so here, the derived-class BasePlusCommissionEmployee
(Fig. 11.15) can invoke non-private
base-class member functions (setFirstName
, getFirstName
, setLastName
, getLastName
, setSocialSecurityNumber
and getSocialSecurityNumber
) to manipulate these data members, as can any client code of class BasePlusCommissionEmployee
(such as main
).
In the body of the constructor and in the bodies of member functions earnings
(Fig. 11.14, lines 70–72) and toString
(lines 75–84), we call the class’s set and get member functions to access the class’s private
data members. If we decide to change the data member names, the earnings
and toString
definitions will not require modification—only the definitions of the get and set member functions that directly manipulate the data members will need to change. These changes occur solely within the base class—no changes to the derived class are needed. Localizing the effects of changes like this is a good software engineering practice.
Using a member function to access a data member’s value can be slightly slower than accessing the data directly. However, today’s optimizing compilers perform many optimizations implicitly (such as inlining set and get member-function calls). You should write code that adheres to proper software engineering principles, and leave optimization to the compiler. A good rule is, “Do not second-guess the compiler.”
BasePlusCommissionEmployee
’s Member-Function DefinitionsClass BasePlusCommissionEmployee
inherits CommissionEmployee
’s public
member functions and can access the private
base-class members via the inherited member functions. The class’s header remains unchanged from Fig. 11.10. The class has several changes to its member-function implementations (Fig. 11.15) that distinguish it from the previous version of the class (Figs. 11.10–11.11). Member functions earnings
(Fig. 11.15, lines 32–34) and toString
(lines 37–42) each invoke member function getBaseSalary
to obtain the base salary value, rather than accessing baseSalary
directly. This insulates earnings
and toString
from potential changes to the implementation of data member baseSalary
. For example, if we decide to rename data member baseSalary
or change its type, only member functions setBaseSalary
and getBaseSalary
will need to change.
BasePlusCommissionEmployee
Member Function earnings
Class BasePlusCommissionEmployee
’s earnings
function (Fig. 11.15, lines 32–34) redefines class CommissionEmployee
’s earnings
member function (Fig. 11.14, lines 70–72) to calculate the earnings of a BasePlusCommissionEmployee
. Class BasePlusCommissionEmployee
’s version of earnings
obtains the portion of the employee’s earnings based on commission alone by calling base-class CommissionEmployee
’s earnings
function with the expression CommissionEmployee::earnings()
as shown in line 33 of Fig. 11.15. BasePlusCommissionEmployee
’s earnings
function then adds the base salary to this value to calculate the total earnings of the employee. Note the syntax used to invoke a redefined base-class member function from a derived class—place the base-class name and the scope resolution operator (::
) before the base-class member-function name. This member-function invocation is a good software engineering practice: Recall from Chapter 9 that, if an object’s member function performs the actions needed by another object, we should call that member function rather than duplicating its code body. By having BasePlusCommissionEmployee
’s earnings
function invoke CommissionEmployee
’s earnings
function to calculate part of a BasePlusCommissionEmployee
object’s earnings, we avoid duplicating the code and reduce code-maintenance problems.
When a base-class member function is redefined in a derived class, the derived-class version often calls the base-class version to do additional work. Failure to use the ::
operator prefixed with the name of the base class when referencing the base class’s member function causes infinite recursion, because the derived-class member function would then call itself.
BasePlusCommissionEmployee
Member Function toString
Similarly, BasePlusCommissionEmployee
’s toString
function (Fig. 11.15, lines 37–42) redefines class CommissionEmployee
’s toString
function (Fig. 11.14, lines 75–84) to output the appropriate base-salaried commission employee information. The new version displays "base-salaried"
followed by the part of a BasePlusCommissionEmployee
object’s information returned by calling CommissionEmployee
’s toString
member function with the qualified name CommissionEmployee::toString()
(Fig. 11.15, line 39)—this returns a string
containing "commission
employee"
and the values of class CommissionEmployee
’s private
data members. BasePlusCommissionEmployee
’s toString
function then outputs the remainder of a BasePlusCommissionEmployee
object’s information (i.e., the value of class BasePlusCommissionEmployee
’s base salary preceded by "base
salary:"
).
Once again, this example uses the BasePlusCommissionEmployee
test program from Fig. 11.9 and produces the same output. Although each “base-salaried commission employee” class behaves identically, the version in this example is the best engineered. By using inheritance and by calling member functions that hide the data and ensure consistency, we’ve efficiently and effectively constructed a well-engineered class.
CommissionEmployee–BasePlusCommissionEmployee
ExamplesIn this section, you saw an evolutionary set of examples that was designed to teach key capabilities for good software engineering with inheritance. You learned how to create a derived class using inheritance, how to use protected
base-class members to enable a derived class to access inherited base-class data members and how to redefine base-class functions to provide versions that are more appropriate for derived-class objects. In addition, you learned how to apply software engineering techniques from Chapter 9 and this chapter to create classes that are easy to maintain, modify and debug.