Inheritance
A checking account and a savings account both have a balance and both take deposits; the savings account also earns interest. Write them as two separate classes and the balance and deposit are written twice, and a bug fixed in one copy stays in the other. Inheritance lets you write the shared part once, in one class, and build the other class on top of it:
1#include <iostream>2 3class Account {4public:5 void deposit(int amount) {6 balance_ += amount;7 }8 9 int balance() const {10 return balance_;11 }12 13private:14 int balance_ = 0;15};16 17class SavingsAccount : public Account {18public:19 void add_interest(int percent) {20 deposit(balance() * percent / 100);21 }22};23 24int main() {25 SavingsAccount s;26 s.deposit(200);27 s.add_interest(5);28 std::cout << "Balance: " << s.balance() << '\n';29 return 0;30}
Balance: 210
SavingsAccount never declares deposit, balance or balance_, yet s.deposit(200) works: it got them all from Account. It only adds what's new, add_interest. Press Step: inside s there's an <Account> part first, holding balance_, and that part changes when you deposit.
Base class and derived class
class SavingsAccount : public Account reads "a SavingsAccount is an Account, plus what's written here". Account is the base class (also called the parent class), and SavingsAccount is the derived class (also called the child class). One base can have many derived classes: a checking account, a savings account and a kids' account can all be built on Account.
The derived class gets every member of the base, but the private ones are still private to the base: the derived class's own functions can't touch them either. Try to add the interest straight to balance_:
1#include <iostream>2 3class Account {4public:5 void deposit(int amount) {6 balance_ += amount;7 }8 9 int balance() const {10 return balance_;11 }12 13private:14 int balance_ = 0;15};16 17class SavingsAccount : public Account {18public:19 void add_interest(int percent) {20 balance_ += balance_ * percent / 100;21 }22};23 24int main() {25 SavingsAccount s;26 s.deposit(200);27 s.add_interest(5);28 std::cout << "Balance: " << s.balance() << '\n';29 return 0;30}
main.cpp: In member function 'void SavingsAccount::add_interest(int)':
main.cpp:20:9: error: 'int Account::balance_' is private within this context
20 | balance_ += balance_ * percent / 100;
| ^~~~~~~~
main.cpp:14:9: note: declared private here
14 | int balance_ = 0;
| ^~~~~~~~'int Account::balance_' is private within this context. Private means only Account's own member functions, not even its derived classes. That's why the first example went through the public deposit and balance.
Note: Always write the public in : public Account. Without it, class inherits privately, which is something else (this course doesn't use it).
protected
Between public and private there's a third level, **protected**: the class's own functions and its derived classes' functions can use it; code outside still can't.
1#include <iostream>2 3class Account {4public:5 void deposit(int amount) {6 balance_ += amount;7 }8 9 int balance() const {10 return balance_;11 }12 13protected:14 int balance_ = 0;15};16 17class SavingsAccount : public Account {18public:19 void add_interest(int percent) {20 balance_ += balance_ * percent / 100;21 }22};23 24int main() {25 SavingsAccount s;26 s.deposit(200);27 s.add_interest(5);28 s.balance_ = 1000000;29 std::cout << "Balance: " << s.balance() << '\n';30 return 0;31}
main.cpp: In function 'int main()':
main.cpp:28:7: error: 'int Account::balance_' is protected within this context
28 | s.balance_ = 1000000;
| ^~~~~~~~
main.cpp:14:9: note: declared protected here
14 | int balance_ = 0;
| ^~~~~~~~This time add_interest compiles fine. The error is on line 28, in main: 'int Account::balance_' is protected within this context. To main, protected is the same as private. Delete that line and it shows Balance: 210.
Tip: protected data can be changed by every derived class anyone ever writes, so it's less safe than private. Keep data private when public member functions are enough, as in the first example; use protected for what derived classes really need.
Constructors: base first
A derived object has a base part inside, and the base part is built by the base's constructor. The derived constructor calls it in its initializer list, by the base's name: : Animal(name). Have every constructor and destructor show a line and you can see the order:
1#include <iostream>2#include <string>3 4class Animal {5public:6 Animal(const std::string& name) : name_(name) {7 std::cout << "Animal " << name_ << " created\n";8 }9 10 ~Animal() {11 std::cout << "Animal " << name_ << " destroyed\n";12 }13 14protected:15 std::string name_;16};17 18class Dog : public Animal {19public:20 Dog(const std::string& name, const std::string& breed) : Animal(name), breed_(breed) {21 std::cout << "Dog " << name_ << " (" << breed_ << ") created\n";22 }23 24 ~Dog() {25 std::cout << "Dog " << name_ << " destroyed\n";26 }27 28private:29 std::string breed_;30};31 32int main() {33 Dog d{"Rex", "beagle"};34 std::cout << "--- main ends\n";35 return 0;36}
Animal Rex created Dog Rex (beagle) created --- main ends Dog Rex destroyed Animal Rex destroyed
Building goes base first: the Animal part, then Dog's own members, then the body of Dog's constructor, which by then can safely use name_. Taking apart goes the other way: Dog's destructor first, then Animal's. It's like a house: foundation first, roof last; when it comes down, the roof goes first. Press Step at line 33 and watch the program go into Dog's constructor, jump to Animal's, and come back.
If you leave Animal(name) out of the initializer list, the compiler tries to build the base part with Animal(), which doesn't exist here, and you get no matching function for call to 'Animal::Animal()'.
What does this show?
1#include <iostream>2 3class Vehicle {4public:5 Vehicle() {6 std::cout << "V+ ";7 }8 9 ~Vehicle() {10 std::cout << "V- ";11 }12};13 14class Car : public Vehicle {15public:16 Car() {17 std::cout << "C+ ";18 }19 20 ~Car() {21 std::cout << "C- ";22 }23};24 25int main() {26 Car c;27 return 0;28}
Car() doesn't name a base constructor, so Vehicle() runs, and it runs before Car's body. When c dies, the order is reversed: Car's destructor first, then Vehicle's.
Redefining a function
A derived class can write a function with the same name as one in the base. A manager is described like any employee, plus the size of the team:
1#include <iostream>2#include <string>3 4class Employee {5public:6 Employee(const std::string& name, int salary) : name_(name), salary_(salary) {}7 8 void describe() const {9 std::cout << name_ << ", salary " << salary_ << '\n';10 }11 12protected:13 std::string name_;14 int salary_;15};16 17class Manager : public Employee {18public:19 Manager(const std::string& name, int salary, int team) : Employee(name, salary), team_(team) {}20 21 void describe() const {22 Employee::describe();23 std::cout << " manages " << team_ << " people\n";24 }25 26private:27 int team_;28};29 30int main() {31 Employee e{"Ana", 4000};32 Manager m{"Ben", 6000, 5};33 e.describe();34 m.describe();35 return 0;36}
Ana, salary 4000 Ben, salary 6000 manages 5 people
m.describe() finds Manager's describe, which hides the one in Employee. Inside it, Employee::describe() calls the base's version by its full name, so the first line isn't written twice. Leave out the Employee:: and describe() calls Manager's version, which is itself, again and again, without end.
A derived object can be used as its base
A Manager is an Employee, so wherever an Employee is wanted, a Manager can go. A function that takes const Employee& works for both:
1#include <iostream>2#include <string>3 4class Employee {5public:6 Employee(const std::string& name, int salary) : name_(name), salary_(salary) {}7 8 const std::string& name() const {9 return name_;10 }11 12protected:13 std::string name_;14 int salary_;15};16 17class Manager : public Employee {18public:19 Manager(const std::string& name, int salary, int team) : Employee(name, salary), team_(team) {}20 21private:22 int team_;23};24 25void show_badge(const Employee& e) {26 std::cout << "[ " << e.name() << " ]\n";27}28 29int main() {30 Employee e{"Ana", 4000};31 Manager m{"Ben", 6000, 5};32 show_badge(e);33 show_badge(m);34 Employee* p = &m;35 std::cout << p->name() << '\n';36 return 0;37}
[ Ana ] [ Ben ] Ben
One show_badge for every kind of employee: the reference e sees only the Employee part of m. A pointer works the same way: Employee* p = &m;. The other way round doesn't: not every Employee is a Manager, so Manager* q = &e; is a compile error.
Seeing only the Employee part has a catch:
What does this show?
1#include <iostream>2#include <string>3 4class Employee {5public:6 void describe() const {7 std::cout << "Employee\n";8 }9};10 11class Manager : public Employee {12public:13 void describe() const {14 std::cout << "Manager\n";15 }16};17 18int main() {19 Manager m;20 Employee* p = &m;21 p->describe();22 return 0;23}
p is an Employee*, and the compiler picks the function from the type of the pointer, not from the object it points to. So Employee::describe runs, even though p points to a Manager. The next lesson's virtual changes this.
Copying into a base object
A pointer or a reference to the base still leads to the whole object. Copying into a base object is different: a variable of type Employee only has room for the Employee part, so the rest is cut off. This is called slicing:
1#include <iostream>2#include <string>3 4class Employee {5public:6 Employee(const std::string& name, int salary) : name_(name), salary_(salary) {}7 8 void describe() const {9 std::cout << name_ << ", salary " << salary_ << '\n';10 }11 12protected:13 std::string name_;14 int salary_;15};16 17class Manager : public Employee {18public:19 Manager(const std::string& name, int salary, int team) : Employee(name, salary), team_(team) {}20 21private:22 int team_;23};24 25int main() {26 Manager m{"Ben", 6000, 5};27 Employee copy = m;28 copy.describe();29 return 0;30}
Ben, salary 6000
copy is a plain Employee with Ben's name and salary; team_ didn't come along. Press Step: m has an <Employee> part and team_, while copy has only the name and the salary. To use a derived object as its base, pass a reference or a pointer, not a copy.
Practice
Write the code, then press Check. A hint costs nothing; the answer is there when you need it.
Books are products too
✓Write a class Book derived from Product. It also has an author; its constructor takes the name, price and author, and its show shows the product line, then by and the author on a line of its own. The program should show the four lines below.
Dune: 12 dollars by Frank Herbert Emma: 9 dollars by Jane Austen
1#include <iostream>2#include <string>3 4class Product {5public:6 Product(const std::string& name, int price) : name_(name), price_(price) {}7 8 void show() const {9 std::cout << name_ << ": " << price_ << " dollars\n";10 }11 12private:13 std::string name_;14 int price_;15};16 17// write class Book here18 19int main() {20 Book a{"Dune", 12, "Frank Herbert"};21 Book b{"Emma", 9, "Jane Austen"};22 a.show();23 b.show();24 return 0;25}
Fix it: a boost that can't reach the speed
✓SportsCar::boost doesn't compile. Change one word in Vehicle so derived classes can change speed_ while it stays hidden from outside the class (main must not be able to change it, so public is out). The program should show 130 180.
130 180
1#include <iostream>2 3class Vehicle {4public:5 Vehicle(int speed) : speed_(speed) {}6 7 int speed() const {8 return speed_;9 }10 11private:12 int speed_;13};14 15class SportsCar : public Vehicle {16public:17 SportsCar(int speed) : Vehicle(speed) {}18 19 void boost() {20 speed_ += 30;21 }22};23 24int main() {25 SportsCar a{100};26 SportsCar b{120};27 a.boost();28 b.boost();29 b.boost();30 std::cout << a.speed() << " " << b.speed() << '\n';31 return 0;32}
Check in, check out
✓Give Suite a constructor that takes a room number and shows Suite 101: minibar filled, and a destructor that shows Suite 101: minibar emptied (with its own number). Room already shows its lines. The output should match exactly.
Room 101 ready Suite 101: minibar filled Room 102 ready Suite 102: minibar filled Lights out Suite 102: minibar emptied Room 102 cleaned Suite 101: minibar emptied Room 101 cleaned
1#include <iostream>2 3class Room {4public:5 Room(int number) : number_(number) {6 std::cout << "Room " << number_ << " ready\n";7 }8 9 ~Room() {10 std::cout << "Room " << number_ << " cleaned\n";11 }12 13protected:14 int number_;15};16 17class Suite : public Room {18public:19};20 21int main() {22 Suite a{101};23 Suite b{102};24 std::cout << "Lights out\n";25 return 0;26}
What you learned
class Derived : public Basebuilds a class on top of another: the derived class gets the base's members and adds its own. Always writepublic.privatemembers of the base are off limits even in derived classes;protectedones can be used by derived classes but not from outside.- The derived constructor builds the base part in its initializer list:
: Animal(name). Building goes base first; destructors run the other way, derived first. - A function with the same name in the derived class hides the base's;
Base::f()calls the base's version. - A derived object can be passed as a
Base&or pointed to by aBase*. Through them you get the base's functions, even if the derived class redefined them; lesson 24 fixes that. - Copying a derived object into a base object keeps only the base part (slicing).