Problem overview
A company pays every employee a salary. A manager is an employee too, but earns a bonus on top of the salary. The task is to write two classes so that a manager reuses everything an employee has and changes only how its pay is worked out.
Employee has a private name and a private salary. Its pay() returns the salary, and its describe() returns text such as Ana earns 3000, using whatever pay() returns. Manager inherits from Employee. Its constructor takes a name, a salary and a bonus; it lets the parent's constructor store the name and salary and keeps the bonus itself, privately. Its pay() returns the parent's pay plus the bonus. describe() is not written again.
For Employee("Ana", 3000) the description is Ana earns 3000. For Manager("Bob", 4000, 1000) it is Bob earns 5000: the same describe() code, but a different pay().
Approach
The design has three ideas.
- Inheritance.
class Manager : public EmployeemakesManagerget the name, the salary and both methods.Manageradds only what is new: the bonus. - Overriding.
Managerreplacespay()with its own version. For that to work whendescribe()callspay()from insideEmployee, the parent'spay()must bevirtual. Then the call picks the version that belongs to the object's real class. - Calling the parent's version. The salary is private to
Employee, soManagercan't read it. It doesn't need to:Employee::pay()runs the parent's method, and the manager adds its bonus to the result. C++ has nosuper; the class name and::do the job.
Every method does a constant amount of work apart from building the text, which is proportional to the length of the name.
Step by step
- Declare
Employee's fields in itsprivate:section: astring nameand anint salary. Private means that only code insideEmployeecan use them, not evenManager. - Store them in the constructor with
this->name = name;andthis->salary = salary;. - Write
pay(), which returnssalary, and mark itvirtualso thatManagercan replace it, even whendescribe()calls it from insideEmployee. - Write
describe(). It returns the name, then" earns ", thento_string(pay()), joined with+. It callspay()instead of readingsalary, so on a manager the bonus shows up without a seconddescribe(). - Declare
Managerwith: public Employee. It already has the name, the salary,pay()anddescribe(). Give it its own privateint bonus. - Write the constructor. The header's
: Employee(name, salary)runsEmployee's constructor first and passes the name and the salary to it, so the private fields are filled in by the code that owns them. The body only storesthis->bonus = bonus;. - Override
pay()withint pay() overrideand returnEmployee::pay() + bonus. Writing plainpay()there would call this same method again and never stop.
The solution in C++
class Employee {
private:
string name;
int salary;
public:
Employee(string name, int salary) {
this->name = name;
this->salary = salary;
}
virtual int pay() {
return salary;
}
string describe() {
return name + " earns " + to_string(pay());
}
};
class Manager : public Employee {
private:
int bonus;
public:
Manager(string name, int salary, int bonus) : Employee(name, salary) {
this->bonus = bonus;
}
int pay() override {
return Employee::pay() + bonus;
}
};
A run
Here are two people from the first example, described through the same method:
#include <iostream>
#include <string>
using namespace std;
// the class above goes here
int main() {
Employee ana("Ana", 3000);
Manager bob("Bob", 4000, 1000);
cout << ana.describe() << endl;
cout << bob.describe() << endl;
Employee* p = &bob;
cout << p->pay() << endl;
Employee copy = bob;
cout << copy.pay() << endl;
}
It prints:
Ana earns 3000
Bob earns 5000
5000
4000
Manager never defines describe(), yet Bob's description shows 5000. The inherited describe() calls pay(), and because pay() is virtual, an object that is a Manager runs Manager::pay().
Why virtual matters
Without virtual, a call to pay() from inside Employee's code always means Employee::pay(), whatever the real type of the object. This version leaves it out:
#include <iostream>
#include <string>
using namespace std;
class Staff {
private:
string name;
int salary;
public:
Staff(string name, int salary) {
this->name = name;
this->salary = salary;
}
int pay() {
return salary;
}
string describe() {
return name + " earns " + to_string(pay());
}
};
class Boss : public Staff {
private:
int bonus;
public:
Boss(string name, int salary, int bonus) : Staff(name, salary) {
this->bonus = bonus;
}
int pay() {
return Staff::pay() + bonus;
}
};
int main() {
Boss bob("Bob", 4000, 1000);
cout << bob.describe() << endl;
cout << bob.pay() << endl;
}
It prints:
Bob earns 4000
5000
The manager's description ignores the bonus, because describe() is part of Employee and calls Employee's pay(). With virtual it prints 5000, as in the lesson. The keyword override on Manager::pay() adds a safety check: if the signature doesn't match a virtual function of the parent, for example because of a typo in the name, the compiler reports an error instead of quietly creating an unrelated method.
Details worth knowing
- Private fields stay private to the class. If
Manager::pay()tried to writesalary + bonus, the compiler would reject it with'int Employee::salary' is private within this context. CallingEmployee::pay()is the intended route. - Objects keep their own type. A
Managervariable runsManager::pay()and anEmployeevariable runsEmployee::pay(). Copying a manager into anEmployeevariable keeps only theEmployeepart, which is called slicing: the copy pays 4000 for Bob. Through a pointer or reference to the manager,pay()still gives 5000. - The order of construction. The parent's constructor always runs before the child's body, so the child can rely on the parent part being ready.
- A bonus of 0 makes a manager earn exactly the salary, like an employee.
Common mistakes
- Forgetting
virtualon the parent'spay(). The code compiles, and managers silently lose their bonus in descriptions. - Calling
pay()insideManager::pay()instead ofEmployee::pay(). The method then calls itself forever. - Making
nameandsalarypublic so thatManagercan use them. The lesson keeps them private on purpose; the parent's methods are the way in. - Copying the describe logic into
Manager. The point of inheritance is that it isn't needed.
