C++ is an object-oriented language and inheritance can be used to define relationships between objects in the form of class hierarchies. It provides a mean to structure and organize your code. Assuming that you already know the basics of inheritance, we will look at how and when to best use it. Especially we look at how to use it for runtime and compile time polymorphism.

Before you start using inheritance to describe your object’s relationships, consider also other alternatives. Often composition is a better approach to structure your code. A simple guideline is to use composition when the relationship between your objects can be described with a has-a relationship e.g. the Robot class has a Leg). And use inheritance when it is better described with an is-a relationship e.g. a Dog is an Animal. However that does not completely work in some cases. It is better to follow the Liskov Substitution Principle. This principle basically states, that if you chose inheritance, then the Derived class should be able to be used anywhere, where the Base class is used i.e. you could substitute all your Deriveds with Bases.

To give a simple example: At first it might seem like a good idea to make your Penguin class inherit from the Bird class because a pengin is a bird. However this does not work well in code if the Bird class has a fly() method in its interface. So following Liskov, we should not use this inheritance, because we could not use Penguin everywhere we used Bird.

Liskov Meme

When is it then a good idea to use inheritance? Often it is to used together with virtual functions for runtime polymorphism. Following the don’t repeat yourself principle we want to write code that does the same thing for similar objects, but we do not want to write the same code for every single class again.

Runtime Polymorphism

So let’s assume we have a PositionSensor and a AccelerationSensor class. However our program should detect during runtime how many of each are present and should store them in a container. So a possible solution is to introduce a Sensor base class and use this base class’ interface in the part of the code that deals with sensors in a generic way.

struct Sensor {
  virtual double measure_value() = 0;
  virtual ~Sensor() = default;
};

struct AccelerationSensor : Sensor {
  double measure_value() override { ... }
};

struct PositionSensor : Sensor {
  double measure_value() override { ... }
};

std::vector<std::unique_ptr<Sensor>> detect(Hardware* hw){
  std::vector<std::unique_ptr<Sensor>> sensors;
  for (size_t i = 0; i < hw->num_sensors(); ++i) {
    auto type = hw->get_next_sensor_type();
    switch (type):
    case SensorType::Position:
      sensors.emplace_back(std::make_unique<PositionSensor>());
      break;
    case SensorType::Acceleration:
      sensors.emplace_back(std::make_unique<AccelerationSensor>());
      break;
    default:
      throw std::runtime_error("Sensor type not supported");
  }
  return sensors;
}

...

GUI::update_sensor_values(std::vector<std::unique_ptr<Sensor>>& sensors) {
  for (size_t i = 0; i < sensors.size(); ++i) {
    display_values_.at(i) = sensors.at(i)->measure_value();
  }
}

And then use it like this:

Hardware hw = load_HW_from_configuration_file();
auto sensors = detect(hw);
GUI gui;
gui.update_sensor_values(sensors);

The example above demonstrates a very basic use of inheritance to achieve runtime polymorphism through virtual function calls.

Compile time Polymorphism

In some cases you want to write generic code, but you already know your polymorphic types at compile time and you don’t want to pay the runtime cost of virtual function calls. One way of achieving this in C++ is called the Curriously Recurring Template Pattern (CRTP) idiom. It’s main idea is to use the derived class as a template parameter of the base class.

template <typename T>
struct Base {
...
};


struct Derived : Base<Derived> {
...
};

If you see this for the first time it might look a bit weird. The Derived class inherits from the Base class with itself as a template parameter. But this way we have the possiblity to access the methods of the derived class in the base class. And if we name the method of the derived class the same we can overwrite the base class method. But let’s just change above example to compile time polymorphism to better see how this works:

template <typename T>
struct Sensor {
  double measure_value() {
    T& derived = static_cast<T&>(*this);
    return derived.measure_value();
  } 
};

struct AccelerationSensor : Sensor<AccelerationSensor> {
  double measure_value() { ... }
};

struct PositionSensor : Sensor<PositionSensor> {
  double measure_value() { ... }
};

...

template <typename Derived>
GUI::update_sensor_value(int i, Sensor<Derived>& sensor) {
  display_values_.at(i) = sensor.measure_value();
}

And then use it like this:

AccelerationSensor sensor0{};
PositionSensor sensor1{};
GUI gui;
gui.update_sensor_value(0, sensor0);
gui.update_sensor_value(1, sensor1);

Now this example is of course a bit oversimplified and you could achieve the same, with a simple templated update_sensor_value function. But as soon as you have more logic built into your classes you will see that the CRTP is a good way to write generic code for types you already know at compile time.

As a final note: Remember that inheritance is not the only way to achieve polymorphism and consider other choices as well e.g. using std::variant.

References

  • Hands-On Design Patterns with C++ by Fedor G. Pikus (ISBN 978-1-78883-256-4)
  • Fluent C++