Modern object-oriented (OO) languages provide 3 capabilities:
which can improve the design, structure and reusability of code.
Here, we'll explore how the object-oriented (OO) programming capability known as inheritance can be used in C++.
All code examples are available for download.
Real-world entities, like employees, are naturally described by both data and functionality.
We will represent different types of employees:
For these employees, we'll store data, like their:
And...we'll require some functionality, like being able to:
Employee
class:Object-oriented languages typically provide a natural way to treat data and functionality as a single entity. In C++, we do so by creating a class.
Here is a class definition for a generic Employee
:
class Employee { public: Employee(string theName, float thePayRate); string getName() const; float getPayRate() const; float pay(float hoursWorked) const; protected: string name; float payRate; };
The class consists of:
Definitions for each of the methods follow:
Employee::Employee(string theName, float thePayRate) { name = theName; payRate = thePayRate; } string Employee::getName() const { return name; } float Employee::getPayRate() const { return payRate; } float Employee::pay(float hoursWorked) const { return hoursWorked * payRate; }
Note that the payRate
is used as an hourly wage.
#include "employee.h" ... Employee empl("John Burke", 25.0); // Print out name and pay (based on 40 hours work). cout << "Name: " << empl.getName() << endl; cout << "Pay: " << empl.pay(40.0) << endl;
Manager
class:In the real world, we don't view everything as unique; we often view something as being like something else but with differences or additions.
Managers are like regular employees; however, there might be differences. For example, they might be paid by a salary.
Our first attempt to write a class for a manager gives the following class definition:
It mainly differs fromclass Manager { public: Manager(string theName, float thePayRate, bool isSalaried); string getName() const; float getPayRate() const; bool getSalaried() const; float pay(float hoursWorked) const; protected: string name; float payRate; bool salaried; };
Employee
in that it has an
additional field (salaried
) and method
(getSalaried()
).
The method definitions for class Manager
do not differ
much from Employee
either:
Manager::Manager(string theName, float thePayRate, bool isSalaried) { name = theName; payRate = thePayRate; salaried = isSalaried; } string Manager::getName() const { return name; } float Manager::getPayRate() const { return payRate; } bool Manager::getSalaried() const { return salaried; } float Manager::pay(float hoursWorked) const { if (salaried) return payRate; /* else */ return hoursWorked * payRate; }
They add very little new code to what was
written in
Compared to
Finally, the
We have done unnecessary work to create
We can fix this using the OO concept of inheritance. If we
let a manager inherit from an employee, then it will get all the data
and functionality of an employee. We can then add any new data and
methods needed for a manager and redefine any methods that
differ for a manager.
Here, we show a new implementation of
The line:
The only things included in the class definition are:
Like this new class definition, the method definitions are also simplified:
For the
that does some of the same work as the
The only way to pass values to
A member initialization list follows a constructor's parameter list.
It consists of a colon (
Without doing so,
For those using an object (versus those defining a
class), "protected" works like the "private" access specifier:
Note that when we call
Remember, it inherited all the data and methods of an
Since we now have one class that inherits from another, we have the
beginnings of a class hierarchy:
If needed, this hierarchy could be extended to include more classes.
Which hierarchy would we choose?
If a supervisor is viewed as part of management, then choice a) is
probably your answer. Nonetheless, this is a decision not to be taken
lightly. How one designs the inheritance hierarchy greatly affects
what you can do with those classes later.
Take the code we've provided for the
Add methods to the classes named:
that let users change the corresponding fields. Take advantage of
the inheritance relationship between
Write a
What class should
Your code should compile and run correctly with the test program Employee
.
Employee
, in Manager
...
getName()
and getPayRate()
are identical to those in Employee
.
getSalaried()
is new.
pay()
method work differently.
Nonetheless, they do some of the same work as their counterparts in the
Employee
class.
payRate
has 2 possible uses in the
Manager
class...
If the manager is salaried,
float Manager::pay(float hoursWorked) const
{
if (salaried)
return payRate;
/* else */
return hoursWorked * payRate;
}
payRate
is the fixed rate for
the pay period; otherwise, it represents an hourly rate, just like it
does for a regular employee.
Such a Manager
can be used in a similar manner to an
Employee
:
#include "manager0.h"
...
Manager mgr("Jan Kovacs", 1200.0, true);
// Print out name and pay (based on 40 hours work).
cout << "Name: " << mgr.getName() << endl;
cout << "Pay: " << mgr.pay(40.0) << endl;
Manager
, which is
similar to (and really is a "kind of") Employee
.
Manager
that
inherits from Employee
:
#include "employee.h"
class Manager : public Employee {
public:
Manager(string theName,
float thePayRate,
bool isSalaried);
bool getSalaried() const;
float pay(float hoursWorked) const;
protected:
bool salaried;
};
causes
class Manager : public Employee {
Manager
to inherit all the data and methods of
Employee
.
Note:
Although other access specifiers (besides "public") can be used with
inheritance, we will only discuss public inheritance here.
salaried
,
getSalaried()
,
pay()
(which is redefined in
Manager
).
There are some things to note about these method definitions...
Manager::Manager(string theName,
float thePayRate,
bool isSalaried)
: Employee(theName, thePayRate)
{
salaried = isSalaried;
}
bool Manager::getSalaried() const
{
return salaried;
}
float Manager::pay(float hoursWorked) const
{
if (salaried)
return payRate;
/* else */
return Employee::pay(hoursWorked);
}
Member initialization list
For constructors that require arguments, you must write a new constructor
for each class.
Note:
Classes don't explicitly inherit constructors.
Manager
class, we needed a constructor:
Manager::Manager(string theName,
float thePayRate,
bool isSalaried)
: Employee(theName, thePayRate)
{
salaried = isSalaried;
}
Employee
constructor. To do so, we reused Employee
's
constructor.
Employee
's constructor in
this context is via a member initialization list.
:
) and a comma-separated list of
inherited class names (and values to be passed to their constructors).
Note:
The member initialization list can also be used to pass values
to constructors of data members. For example,
class SomeClass {
public:
SomeClass();
private:
const int SIZE;
AnotherClass data;
};
SomeClass::SomeClass() : SIZE(10), data("foo")
{
// more initialization code
}
SIZE
could not be initialized (because
its constant) and data
's default constructor (if it has one)
would be used.
The
Methods of protected
access specifierManager
have access to payRate
because it was declared in
Employee
as "protected":
I.e., classes that inherit a "protected" field or method can access them.
float Manager::pay(float hoursWorked) const
{
if (salaried)
return payRate; // Yeah, I can use!
...
}
I.e., the "protected" fields remain inaccessible just as they were in
Manager mgr;
mgr.payRate; // Doesn't work!
Employee
:
Employee empl;
empl.payRate; // Doesn't work!
Calling inherited methods
The pay()
method of Manager
uses a different
calculation if the manager is salaried. Otherwise, it makes the
same calculation as a regular Employee
:
We reused the
float Manager::pay(float hoursWorked) const
{
if (salaried)
return payRate;
/* else */
return Employee::pay(hoursWorked);
}
pay()
method of
Employee
to define the pay()
method of
Manager
.
Employee
's pay()
method:
we must explicitly specify the class from which it comes (i.e., from
which it was inherited). Without doing so, we'd have an infinite
recursive call:
Employee::pay(hoursWorked);
float Manager::pay(float hoursWorked) const
{
...
return pay(hoursWorked); // Calls Manager::pay()!
}
This new Manager
class can be used just like our first attempt:
Excitingly, it has methods from
#include "manager.h"
...
Manager mgr("Jan Kovacs", 1200.0, true);
// Print out name and pay (based on 40 hours work).
cout << "Name: " << mgr.getName() << endl;
cout << "Pay: " << mgr.pay(40.0) << endl;
Employee
, like
getName()
, that we did not declare or define in
Manager
...
Employee
! Thus, we have reused our definition of
an employee to simplify defining a manager.
We say that
Employee
|
Manager
Employee
is the base class and
Manager
is a derived class of Employee
.
Note:
Alternatively, we may call Employee
the superclass
and Manager
the subclass.
Adding a Supervisor
To add another type of employee, such as a supervisor, a new class
can be created. Two choices of where to place a Supervisor
class in the hierarchy are:
a) Employee b) Employee
| / \
Manager Manager Supervisor
|
Supervisor
The Supervisor
class directly inherits from
Manager
and indirectly inherits from
Employee
.
Supervisor
directly inherits from
Employee
.
Aside:
We can say that Supervisor
inherits from
Employee
when there is either a direct or indirect inheritance
relationship.
Employee
class (employee.h
and employee.cpp
) and the
Manager
class (manager.h
and manager.cpp
).
setName()
setPayRate()
setSalaried()
Employee
and Manager
--you only need add each of those methods to
1 class.
Supervisor
class. A supervisor is
responsible for employees in a specific department and must:
getDept()
and setDept()
methods
to access the department field.
Supervisor
inherit from?
empltest.cpp
.