Step 1 of 7 · Reading · ~4 min
Learn
Classes, Inheritance, and Templates
Inheritance and Virtual Functions
By the end of this lesson you can build a small family of classes that share one interface, and write a single function that works on any member of that family and still calls the right code for each one.
A derived class and its base
class Hourly : public Employee makes Hourly a derived class of the base class Employee: every Hourly is also an Employee, and a const Employee& can refer to one. The derived constructor builds its base part first, in its member initializer list, exactly like initializing a member:
class Salaried : public Employee {
double monthly;
public:
explicit Salaried(double m) : monthly{m} {}
double pay() const override { return monthly; }
std::string title() const override { return "salaried"; }
};
class Lead : public Salaried {
public:
explicit Lead(double m) : Salaried{m} {} // hand m to the base constructor
};
Lead has no monthly of its own; it inherits the one inside Salaried, along with both functions. Inheritance can go several levels deep.
Virtual and pure virtual functions
A virtual function is a member function whose call is decided at run time by the object's real type, not by the type of the reference you call it through. = 0 makes it pure virtual: the base declares it but gives no body, so every concrete derived class must supply one. A class with a pure virtual function is abstract: you cannot create an Employee object, only objects of derived classes.
class Employee {
public:
virtual double pay() const = 0;
virtual std::string title() const = 0;
virtual ~Employee() = default;
};
class Hourly : public Employee {
double rate, hours;
public:
Hourly(double r, double h) : rate{r}, hours{h} {}
double pay() const override { return rate * hours; }
std::string title() const override { return "hourly"; }
};
void payslip(const Employee& e) {
std::cout << e.title() << ": " << e.pay() << "\n";
}
Hourly h{20, 7.5};
payslip(h); // hourly: 150
override asks the compiler to check that you really are replacing a base virtual function; a misspelt name or a missing const becomes a compile error instead of a silent new function. The virtual destructor matters when you delete a derived object through a base pointer (Employee* p = new Hourly{20, 8}; delete p;): without virtual, only the base part is destroyed.
Overriding only part of the interface
A class two levels down may override just one function and inherit the rest:
class Lead : public Salaried {
public:
explicit Lead(double m) : Salaried{m} {}
std::string title() const override { return "lead"; }
};
// Lead{4000}: title() is "lead", pay() comes from Salaried
Leave out that override and payslip prints salaried: 4000 for a lead. The code compiles; the output is just wrong.
The trap: no virtual, no dispatch
If the base gives title() a body but no virtual, the call is fixed at compile time by the reference type:
class Staff {
public:
std::string title() const { return "staff"; } // not virtual
};
class Intern : public Staff {
public:
std::string title() const { return "intern"; }
};
void show(const Staff& s) { std::cout << s.title() << "\n"; }
Intern i;
show(i); // staff (no virtual: static dispatch)
// intern (with virtual in Staff)
Common mistakes
- Leaving out a factor in one class's formula. A formula with a factor of one half is easy to drop; the program compiles and every result from that class comes out exactly double.
- Not overriding the name in a class derived from a concrete class. The inherited version runs, so the object reports its parent's name, like
salariedabove. - Bypassing the shared function. Printing inside each branch instead of through one function taking
const Base&lets the formats drift apart, and one branch ends up with a missing space or label. - Forgetting the empty case. When the count is 0 the loop never runs, but the summary line must still print.
Your exercise
Build the Shape hierarchy described in the exercise: an abstract base, three shapes (one derived from another), and the report function that prints any of them through a const Shape&. The starter already parses every line and prints the total; the most likely slip is a square that calls itself a rectangle.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…