If there is anything that we wish to change in the child, we should first examine it and see whether it is not something that could better be changed in ourselves.
CARL GUSTAV JUNG, The Integration of the Personality
One of the most powerful features of C++ is the use of inheritance to derive one class from another. Inheritance is the process by which a new class—known as a derived class—is created from another class, called the base class. A derived class automatically has all the member variables and functions that the base class has and can have additional member functions and/or additional member variables.
In Chapter 10, we noted that saying that class D
is derived from another class B
means that class D
has all the features of class B
and some extra, added features as well. When a class D
is derived from a class B
, we say that B
is the base class and D
is the derived class. We also say that D
is the child class and B
is the parent class.1
As an example to illustrate the usefulness of inheritance, imagine that you’ve set up a home automation system where your garage door and furnace thermostat are networked and accessible from your computer. You would like to control and interrogate the status of these devices (e.g., door is open, thermostat set to 80 degrees) from your computer. This would be much easier to accomplish if there was a consistent interface for these disparate devices. Inheritance lets us do this while providing a way to organize our code without duplication.
First, consider the general concept of a device in the home automation system. Every device must have a model and serial number. Perhaps every device also has a way to query its status. We could model this with a Device
class that has variables for the model and serial number, and a function for the status. The idea is that this class contains functions and properties that are common to every possible device.
Second, consider the garage door. This is a specific type of device in the automation system. In addition to having a model, serial number, and way to query its status like every other device, the garage door device also has a specific function to open or close the door. We can model the garage door with a DoorDevice
class. We will need to add an openClose()
function to this class. The DoorDevice
class is also where we would know how to return the status of the device. At the level of the generic Device
class we don’t have the needed information to return the status of a specific device because at that level we don’t even know what kind of device we are working with. While we need to add functions to DoorDevice
for the status and to open/close the door, it would be nice if we didn’t have to duplicate the variables and code to manipulate the model and serial number that we wrote for the Device
class.
Similarly, the thermostat device will also have a model, serial number, and way to query its status in addition to a function to set the temperature. We can define a ThermostatDevice
class with functions to set the temperature and return the status of the device, but it would be nice if we didn’t again have to duplicate the variables and code to manipulate the model and serial number that we wrote for the Device
class!
We can solve this problem with inheritance. In this case, DoorDevice
“IS-A” Device
and ThermostatDevice
“IS-A” Device
. By defining DoorDevice
and ThermostatDevice
as derived classes from Device
, then these classes (if the programmer specifies it) have access to the model and serial number defined in Device
and we don’t need to re-write any code in the Device
class that deals with these variables. At the same time we can add specific code that is unique to our derived classes. The relationship between these classes is illustrated in Display 15.1.
An object of type DoorDevice
or ThermostatDevice
includes functions and variables defined in Device
, such as model
and serialNumber
.
The status()
function can be overridden. If a DoorDevice
object is treated like a Device
object, then calling status()
will invoke DoorDevice's
status()
function, not Device's
status()
function. This is necessary when the Device
class doesn’t know what to return as a status and only the derived classes can return the information.
Once the inheritance relationship is defined, then if we create an object of type DoorDevice
or ThermostatDevice
we will have access to functions and variables defined in Device
. For example, if thermostat
is a variable of type ThermostatDevice
then we could access thermostat.model
if model is a public string variable in the Device
class. This saves us the work of redefining the code and variables from the Device
class.
We can specify the status()
function to behave a bit differently. When we define the same function in both the base and derived classses then we will see later in the chapter that we have two options: redefine the function or override the function. In this case we want to override the function. If we had an object thermostat
of type ThermostatDevice
, but then treat thermostat
instead like it is of type Device
(for example, by passing thermostat
to a function where the parameter is defined to be of type Device
), then invoking status()
will call the definition associated with ThermostatDevice
rather than the definition associated with Device
. This behavior is important in this case because the Device
class doesn’t know what to return as the status! This topic is explored in more detail in Section 15.3.
For another example where inheritance can be applied, consider the CD account in Chapter 10. We discussed how a CD account is a more specialized version of a savings account. By deriving the class CDAccount
from SavingsAccount
, we automatically inherit all of the SavingsAccount
public functions and variables when we create a CDAccount
object. C++ uses inheritance in predefined classes as well. In using streams for file I/O, the predefined class ifstream
is derived from the (predefined) class istream
by adding member functions such as open
and close
. The stream cin
belongs to the class of all input streams (that is, the class istream
), but it does not belong to the class of input-file streams (that is, does not belong to ifstream
), partly because it lacks the member functions open
and close
of the derived class ifstream
.
Suppose we are designing a record-keeping program that has records for salaried employees and hourly employees. There is a natural hierarchy for grouping these classes. These are all classes of people who share the property of being employees.
Employees who are paid an hourly wage are one subset of employees. Another subset consists of employees who are paid a fixed wage each month or each week. Although the program may not need any type corresponding to the set of all employees, thinking in terms of the more general concept of employees can be useful. For example, all employees have names and Social Security numbers, and the member functions for setting and changing names and Social Security numbers will be the same for salaried and hourly employees.
Within C++ you can define a class called Employee
that includes all employees, whether salaried or hourly, and then use this class to define classes for hourly employees and salaried employees. Displays 15.2 and 15.3 show one possible definition for the class Employee
.
Employee
1 //This is the header file employee.h.
2 //This is the interface for the class Employee.
3 //This is primarily intended to be used as a base class to derive
4 //classes for different kinds of employees.
5 #ifndef EMPLOYEE_H
6 #define EMPLOYEE_H
7 #include <string>
8 using namespace std;
9 namespace employeessavitch
10 {
11 class Employee
12 {
13 public:
14 Employee( );
15 Employee(string theName, string theSSN);
16 string getName( ) const;
17 string getSSN( ) const;
18 double getNetPay( ) const;
19 void setName(string newName);
20 void setSSN(string newSSN);
21 void setNetPay(double newNetPay);
22 void printCheck( ) const;
23 private:
24 string name;
25 string ssn;
26 double netPay;
27 };
28 }//employeessavitch
29 #endif //EMPLOYEE_H
Employee
1 //This is the file: employee.cpp.
2 //This is the implementation for the class Employee.
3 //The interface for the class Employee is in the header file employee.h.
4 #include <string>
5 #include <cstdlib>
6 #include <iostream>
7 #include "employee.h"
8 using namespace std;
9 namespace employeessavitch
10 {
11 Employee::Employee( ) : name("No name yet"), ssn("No number yet"), netPay(0)
12 {
13 //deliberately empty
14 }
15 Employee::Employee(string theName, string theNumber)
16 : name(theName), ssn(theNumber), netPay(0)
17 {
18 //deliberately empty
19 }
20 string Employee::getName( ) const
21 {
22 return name;
23 }
24 string Employee::getSSN( ) const
25 {
26 return ssn;
27 }
28
29 double Employee::getNetPay( ) const
30 {
31 return netPay;
32 }
33 void Employee::setName(string newName)
34 {
35 name = newName;
36 }
37 void Employee::setSSN(string newSSN)
38 {
39 ssn = newSSN;
40 }
41 void Employee::setNetPay (double newNetPay)
42 {
43 netPay = newNetPay;
44 }
45 void Employee::printCheck( ) const
46 {
47 cout << "\nERROR: printCheck FUNCTION CALLED FOR AN \n"
48 << "UNDIFFERENTIATED EMPLOYEE. Aborting the program.\n"
49 << "Check with the author of the program about this bug.\n";
50 exit(1);
51 }
52 }//employeessavitch
You can have an (undifferentiated) Employee
object, but our reason for defining the class Employee
is so that we can define derived classes for different kinds of employees. In particular, the function printCheck
will always have its definition changed in derived classes so that different kinds of employees can have different kinds of checks. This is reflected in the definition of the function printCheck
for the class Employee
(Display 15.3). It makes little sense to print a check for such an (undifferentiated) Employee
. We know nothing about this employee’s salary details. Consequently, we implemented the function printCheck
of the class Employee
so that the program stops with an error message if printCheck
is called for a base class Employee
object. As you will see, derived classes will have enough information to redefine the function printCheck
to produce meaningful employee checks.
A class that is derived from the class Employee
will automatically have all the member variables of the class Employee
(name
, ssn
, and netPay
). A class that is derived from the class Employee
will also have all the member functions of the class Employee
, such as printCheck
, getName
, setName
, and the other member functions listed in Display 15.2. This is usually expressed by saying that the derived class inherits the member variables and member functions.
The interface files with the class definitions of two derived classes of the class Employee
are given in Displays 15.4 (HourlyEmployee
) and 15.5 (SalariedEmployee
). We have placed the class Employee
and the two derived classes in the same namespace. C++ does not require that they be in the same namespace, but since they are related classes, it makes sense to put them there. We will first discuss the derived class HourlyEmployee
given in Display 15.4.
HourlyEmployee
SalariedEmployee
1 //This is the header file salariedemployee.h.
2 //This is the interface for the class SalariedEmployee.
3 #ifndef SALARIEDEMPLOYEE_H
4 #define SALARIEDEMPLOYEE_H
5 #include <string>
6 #include "employee.h"
7 using namespace std;
8 namespace employeessavitch
9 {
10 class SalariedEmployee : public Employee
11 {
12 public:
13 SalariedEmployee( );
14 SalariedEmployee (string theName, string theSSN,
15 double theWeeklySalary);
16 double getSalary( ) const;
17 void setSalary(double newSalary);
18 void printCheck( );
19 private:
20 double salary;//weekly
21 };
22 }//employeessavitch
23 #endif //SALARIEDEMPLOYEE_H
Note that the definition of a derived class begins like any other class definition but adds a colon, the reserved word public
, and the name of the base class to the first line of the class definition, as in the following (from Display 15.4):
class HourlyEmployee :
public Employee
{
By using the keyword public
the derived class (such as HourlyEmployee
) automatically receives all the public member variables and member functions of the base class (such as Employee
). We can also add additional member variables and member functions to the derived class.
The definition of the class HourlyEmployee
does not mention the member variables name
, ssn
, and netPay
, but every object of the class HourlyEmployee
has member variables named name
, ssn
, and netPay
. These member variables are inherited from the class Employee
. The class HourlyEmployee
declares two additional member variables named wageRate
and hours
. Thus, every object of the class HourlyEmployee
has five member variables named name
, ssn
, netPay
, wageRate
, and hours
. Note that the definition of a derived class (such as HourlyEmployee
) only lists the added member variables. The member variables defined in the base class are not mentioned. They are provided automatically to the derived class.
Just as it inherits the member variables of the class Employee
, the class HourlyEmployee
inherits all the member functions from the class Employee
. So, the class HourlyEmployee
inherits the member functions getName
, getSSN
, getNetPay
, setName
, setSSN
, setNetPay
, and printCheck
from the class Employee
.
In addition to the inherited member variables and member functions, a derived class can add new member variables and new member functions. The new member variables and the declarations for the new member functions are listed in the class definition. For example, the derived class HourlyEmployee
adds the two member variables wageRate
and hours
, and it adds the new member functions named setRate
, getRate
, setHours
, and getHours
. This is shown in Display 15.4. Note that you do not give the declarations of the inherited member functions except for those whose definitions you want to change, which is the reason we list only the member function printCheck
from the base class Employee
. For now, do not worry about the details of the constructor definition for the derived class. We will discuss constructors in the next subsection.
In the implementation file for the derived class, such as the implementation of HourlyEmployee
in Display 15.6, you give the definitions of all the added member functions. Note that you do not give definitions for the inherited member functions unless the definition of the member function is changed in the derived class, a point we discuss next.
HourlyEmployee
The definition of an inherited member function can be changed in the definition of a derived class so that it has a meaning in the derived class that is different from what it is in the base class. This is called redefining the inherited member function. For example, the member function printCheck( )
is redefined in the definition of the derived class HourlyEmployee
. To redefine a member function definition, simply list it in the class definition and give it a new definition, just as you would do with a member function that is added in the derived class. This is illustrated by the redefined function printCheck( )
of the class HourlyEmployee
(Displays 15.4 and 15.6).
SalariedEmployee
is another example of a derived class of the class Employee
. The interface for the class SalariedEmployee
is given in Display 15.5. An object declared to be of type SalariedEmployee
has all the member functions and member variables of Employee
and the new members given in the definition of the class SalariedEmployee
. This is true even though the class SalariedEmployee
lists none of the inherited variables and only lists one function from the class Employee
, namely, the function printCheck
, which will have its definition changed in SalariedEmployee
. The class SalariedEmployee
, nonetheless, has the three member variables name
, ssn
, and netPay
, as well as the member variable salary
. Notice that you do not have to declare the member variables and member functions of the class Employee
, such as name
and setName
, in order for a SalariedEmployee
to have these members. The class SalariedEmployee
gets these inherited members automatically without the programmer doing anything.
Note that the class Employee
has all the code that is common to the two classes HourlyEmployee
and SalariedEmployee
. This saves you the trouble of writing identical code two times, once for the class HourlyEmployee
and once for the class SalariedEmployee
. Inheritance allows you to reuse the code in the class Employee
.
A constructor in a base class is not inherited in the derived class, but you can invoke a constructor of the base class within the definition of a derived class constructor, and that is all you need or normally want. A constructor for a derived class uses a constructor from the base class in a special way. A constructor for the base class initializes all the data inherited from the base class. Thus, a constructor for a derived class begins with an invocation of a constructor for the base class.
There is a special syntax for invoking the base class constructor that is illustrated by the constructor definitions for the class HourlyEmployee
given in Display 15.6. In what follows we have reproduced (with minor changes in the line breaks to make it fit the text column) one of the constructor definitions for the class HourlyEmployee
taken from that display:
HourlyEmployee::HourlyEmployee(string theName,
string theNumber, double theWageRate,
double theHours)
: Employee(theName, theNumber),
wageRate(theWageRate), hours(theHours)
{
//deliberately empty
}
The portion after the colon is the initialization section of the constructor definition for the constructor HourlyEmployee::HourlyEmployee
. The part Employee(theName,
theNumber)
is an invocation of the two-argument constructor for the base class Employee
. Note that the syntax for invoking the base class constructor is analogous to the syntax used to set member variables: The entry wageRate(theWageRate)
sets the value of the member variable wageRate
to theWageRate
; the entry Employee(theName,
theNumber)
invokes the base class constructor Employee
with the arguments theName
and theNumber
. Since all the work is done in the initialization section, the body of the constructor definition is empty.
Here we reproduce the other constructor for the class HourlyEmployee
from Display 15.6:
HourlyEmployee::HourlyEmployee( ) : Employee( ), wageRate(0),
hours(0)
{
//deliberately empty
}
In this constructor definition the default (zero-argument) version of the base class constructor is called to initialize the inherited member variables. You should always include an invocation of one of the base class constructors in the initialization section of a derived class constructor.
If a constructor definition for a derived class does not include an invocation of a constructor for the base class, then the default (zero-argument) version of the base class constructor will be invoked automatically. So, the following definition of the default constructor for the class HourlyEmployee
(with Employee( )
omitted) is equivalent to the version we just discussed:
HourlyEmployee::HourlyEmployee( ) : wageRate(0), hours(0)
{
//deliberately empty
}
However, we prefer to always explicitly include a call to a base class constructor, even if it would be invoked automatically.
A derived class object has all the member variables of the base class. When a derived class constructor is called, these member variables need to be allocated memory and should be initialized. This allocation of memory for the inherited member variables must be done by a constructor for the base class, and the base class constructor is the most convenient place to initialize these inherited member variables. That is why you should always include a call to one of the base class constructors when you define a constructor for a derived class. If you do not include a call to a base class constructor (in the initialization section of the definition of a derived class constructor), then the default (zero-argument) constructor of the base class is called automatically. (If there is no default constructor for the base class, that is an error condition.)
The call to the base class constructor is the first action taken by a derived class constructor. Thus, if class B is derived from class A and class C is derived from class B, then when an object of the class C is created, first a constructor for the class A is called, then a constructor for B is called, and finally the remaining actions of the C constructor are taken.
protected
QualifierAs you have seen, you cannot access a private member variable or private member function in the definition or implementation of a derived class. There is a classification of member variables and functions that allows them to be accessed by name in a derived class but not anyplace else, such as in some class that is not a derived class. If you use the qualifier protected
, rather than private
or public
, before a member variable or member function of a class, then for any class or function other than a derived class, the effect is the same as if the member variable were labeled private
; however, in a derived class the variable can be accessed by name.
For example, consider the class HourlyEmployee
that was derived from the base class Employee
. We were required to use accessor and mutator member functions to manipulate the inherited member variables in the definition of HourlyEmployee::printCheck
. If all the private member variables in the class Employee
were labeled with the keyword protected
instead of private
, the definition of HourlyEmployee::printCheck
in the derived class Employee
could be simplified to the following:
void HourlyEmployee::printCheck( )
//Only works if the member variables of Employee are marked
//protected instead of private.
{
netPay = hours * wageRate;
cout << "\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n";
cout << "Pay to the order of " << name << endl;
cout << "The sum of " << netPay << " Dollars\n";
cout << "_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n";
cout << "Check Stub: NOT NEGOTIABLE\n";
cout << "Employee Number: " << ssn << endl;
cout << "Hourly Employee. \nHours worked: " << hours
<< " Rate: " << wageRate << " Pay: " << netPay
<< endl;
cout << "_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n";
}
In the derived class HourlyEmployee
, the inherited member variables name
, netPay
, and ssn
can be accessed by name, provided they are marked as protected
(as opposed to private
) in the base class Employee
. However, in any class that is not derived from the class Employee
, these member variables are treated as if they were marked private
.
Member variables that are protected in the base class act as though they were also marked protected
in any derived class. For example, suppose you define a derived class PartTimeHourlyEmployee
of the class HourlyEmployee
. The class PartTimeHourlyEmployee
inherits all the member variables of the class HourlyEmployee
, including the member variables that HourlyEmployee
inherits from the class Employee
. So, the class PartTimeHourlyEmployee
will have the member variables netPay
, name
, and ssn
. If these member variables were marked protected
in the class Employee
, then they can be used by name in the definitions of functions of the class PartTimeHourlyEmployee
. Except for derived classes (and derived classes of derived classes, etc.), a member variable that is marked protected
is treated the same as if it were marked private
.
We include a discussion of protected
member variables primarily because you will see them used and should be familiar with them. Many, but not all, programming authorities say it is bad style to use protected
member variables. They say it compromises the principle of hiding the class implementation and that all member variables should be marked private
. If all member variables are marked private
, the inherited member variables cannot be accessed by name in derived class function definitions. However, this is not as bad as its sounds. The inherited private
member variables can be accessed indirectly by invoking inherited functions that either read or change the private
inherited variables. Since authorities differ, you will have to make your own decision on whether or not to use protected members.
Is the following program legal (assuming appropriate #include
and using
directives are added)?
void showEmployeeData(const Employee object);
int main( )
{
HourlyEmployee joe("Mighty Joe",
"123-45-6789", 20.50, 40);
SalariedEmployee boss("Mr. Big Shot",
"987-65-4321", 10500.50);
showEmployeeData(joe);
showEmployeeData(boss);
return 0;
}
void showEmployeeData(const Employee object)
{
cout << "Name: " << object.getName( ) << endl;
cout << "Social Security Number: "
<< object.getSSN( ) << endl;
}
Give a definition for a class SmartBut
that is a derived class of the base class Smart
, which we reproduce for you here. Do not bother with #include
directives or namespace details.
class Smart
{
public:
Smart( );
void printAnswer( ) const;
protected:
int a;
int b;
};
This class should have an additional data field, crazy
, that is of type bool
, one additional member function that takes no arguments and returns a value of type bool
, and suitable constructors. The new function is named isCrazy
. You do not need to give any implementations, just the class definition.
Is the following a legal definition of the member function isCrazy
in the derived class SmartBut
discussed in Self-Test Exercise 2? Explain your answer. (Remember, the question asks if it is legal, not if it is a sensible definition.)
bool SmartBut::isCrazy( ) const
{
if (a > b)
return false;
else
return true;
}
In the definition of the derived class HourlyEmployee
(Display 15.4), we gave the declarations for the new member functions setRate
, getRate
, setHours
, and getHours
. We also gave the function declaration for only one of the member functions inherited from the class Employee
. The inherited member functions whose function declarations were not given (such as setName
and setSSN
) are inherited unchanged. They have the same definition in the class HourlyEmployee
as they do in the base class Employee
. When you define a derived class like HourlyEmployee
, you list only the function declarations for the inherited member functions whose definitions you want to change to have a different definition in the derived class. If you look at the implementation of the class HourlyEmployee
, given in Display 15.6, you will see that we have redefined the inherited member function printCheck
. The class SalariedEmployee
also gives a new definition to the member function printCheck
, as shown in Display 15.7. Moreover, the two classes give different definitions from each other. The function printCheck
is redefined in the derived classes.
SalariedEmployee
1 //This is the file salariedemployee.cpp.
2 //This is the implementation for the class SalariedEmployee.
3 //The interface for the class SalariedEmployee is in
4 //the header file salariedemployee.h.
5 #include <iostream>
6 #include <string>
7 #include "salariedemployee.h"
8 using namespace std;
9 namespace employeessavitch
10 {
11 SalariedEmployee::SalariedEmployee( ) : Employee( ), salary(0)
12 {
13 //deliberately empty
14 }
15 SalariedEmployee::SalariedEmployee(string theName, string theNumber,
16 double theWeeklySalary)
17 : Employee(theName, theNumber), salary(theWeeklySalary)
18 {
19 //deliberately empty
20 }
21 double SalariedEmployee::getSalary( ) const
22 {
23 return salary;
24 }
25 void SalariedEmployee::setSalary(double newSalary)
26 {
27 salary = newSalary;
28 }
29 void SalariedEmployee::printCheck( )
30 {
31 setNetPay(salary);
32 cout << "\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n";
33 cout << "Pay to the order of " << getName( ) << endl;
34 cout << "The sum of " << getNetPay( ) << " Dollars\n";
35 cout << "_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n";
36 cout << "Check Stub NOT NEGOTIABLE \n";
37 void SalariedEmployee::printCheck( )
38 {
39 setNetPay(salary);
40 cout << "\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n";
41 cout << "Pay to the order of " << getName( ) << endl;
42 cout << "The sum of " << getNetPay( ) << " Dollars\n";
43 cout << "_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n";
44 cout << "Check Stub NOT NEGOTIABLE \n";
45 cout << "Employee Number: " << getSSN( ) << endl;
46 cout << "Salaried Employee. Regular Pay: "
47 << salary << endl;
48 cout << "_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n";
49 }
50 }//employeessavitch
Display 15.8 gives a demonstration program that illustrates the use of the derived classes HourlyEmployee
and SalariedEmployee
.
1 #include <iostream>
2 #include "hourlyemployee.h"
3 #include "salariedemployee.h"
4 using std::cout;
5 using std::endl;
6 using namespace employeessavitch;
7 int main( )
8 {
9 HourlyEmployee joe;
10 joe.setName("Mighty Joe");
11 joe.setSSN("123-45-6789");
12 joe.setRate(20.50);
13 joe.setHours(40);
14 cout << "Check for " << joe.getName( )
15 << " for " << joe.getHours( ) << " hours.\n";
16 joe.printCheck( );
17 cout << endl;
18 SalariedEmployee boss("Mr. Big Shot", "987-65-4321", 10500.50);
19 cout << "Check for " <<boss.getName( )<< endl;
20 boss.printCheck( );
The functions setName, setSSN, setRate, setHours, and
getName are inherited unchanged from the class Employee. The
21 return 0; function printCheck is redefined. The function getHours was added
22 } to the derived class HourlyEmployee
Sample Dialogue
Check for Mighty Joe for 40 hours. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ Pay to the order of Mighty Joe The sum of 820 Dollars _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ Check Stub: NOT NEGOTIABLE Employee Number: 123-45-6789 Hourly Employee. Hours worked: 40 Rate: 20.5 Pay: 820 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ Check for Mr. Big Shot _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ Pay to the order of Mr. Big Shot The sum of 10500.5 Dollars _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ Check Stub NOT NEGOTIABLE Employee Number: 987-65-4321 Salaried Employee. Regular Pay: 10500.5 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Do not confuse redefining a function definition in a derived class with overloading a function name. When you redefine a function definition, the new function definition given in the derived class has the same number and types of parameters. On the other hand, if the function in the derived class were to have a different number of parameters or a parameter of a different type from the function in the base class, then the derived class would have both functions. That would be overloading. For example, suppose we added a function with the following function declaration to the definition of the class HourlyEmployee
:
void setName(string firstName, string lastName);
The class HourlyEmployee
would have this two-argument function setName
, and it would also inherit the following one-argument function setName
:
void setName(string newName);
The class HourlyEmployee
would have two functions named setName
. This would be overloading the function name setName
.
On the other hand, both the class Employee
and the class HourlyEmployee
define a function with the following function declaration:
void printCheck( );
In this case, the class HourlyEmployee
has only one function named printCheck
, but the definition of the function printCheck
for the class HourlyEmployee
is different from its definition for the class Employee
. In this case, the function printCheck
has been redefined.
If you get redefining and overloading confused, you do have one consolation. They are both legal. So, it is more important to learn how to use them than it is to learn to distinguish between them. Nonetheless, you should learn the difference.
Inheritance Example
Suppose you redefine a function so that it has a different definition in the derived class from what it had in the base class. The definition that was given in the base class is not completely lost to the derived class objects. However, if you want to invoke the version of the function given in the base class with an object in the derived class, you need some way to say “use the definition of this function as given in the base class (even though I am an object of the derived class).” The way you say this is to use the scope resolution operator with the name of the base class. An example should clarify the details.
Consider the base class Employee
(Display 15.2) and the derived class HourlyEmployee
(Display 15.4). The function printCheck( )
is defined in both classes. Now suppose you have an object of each class, as in
Employee janeE;
HourlyEmployee sallyH;
Then
janeE.printCheck( );
uses the definition of printCheck
given in the class Employee
, and
sallyH.printCheck( );
uses the definition of printCheck
given in the class HourlyEmployee
.
But, suppose you want to invoke the version of printCheck
given in the definition of the base class Employee
with the derived class object sallyH
as the calling object for printCheck
. You do that as follows:
sallyH.Employee::printCheck( );
Of course, you are unlikely to want to use the version of printCheck
given in the particular class Employee
, but with other classes and other functions, you may occasionally want to use a function definition from a base class with a derived class object. An example is given in Self-Test Exercise 6.
The class SalariedEmployee
inherits both of the functions getName
and printCheck
(among other things) from the base class Employee
, yet only the function declaration for the function printCheck
is given in the definition of the class SalariedEmployee
. Why isn’t the function declaration for the function getName
given in the definition of SalariedEmployee
?
Give a definition for a class TitledEmployee
that is a derived class of the base class SalariedEmployee
given in Display 15.5. The class TitledEmployee
has one additional member variable of type string
called title
. It also has two additional member functions: getTitle
, which takes no arguments and returns a string
; and setTitle
, which is a void
function that takes one argument of type string
. It also redefines the member function setName
. You do not need to give any implementations, just the class definition. However, do give all needed #include
directives and all using
namespace
directives. Place the class TitledEmployee
in the namespace employeessavitch
.
Give the definitions of the constructors for the class TitledEmployee
that you gave as the answer to Self-Test Exercise 5. Also, give the redefinition of the member function setName
. The function setName
should insert the title into the name. Do not bother with #include
directives or namespace details.