Uses of Inheritance

Contents

C++ is a multi-paradigm language, so it is not a purely object oriented language but has other aspects, for example a huge support for generic programming via templates. One of its major strengths is the ability to mix those different aspects.

Inheritance is one of the mainly object oriented aspects in C++, and in a purely object oriented context it means a “Is-A” relationship. In the mix with other aspects of C++, inheritance can be used for purely technical and other reasons that do not imply object oriented subtyping.

In this post I am going to explore possible uses of inheritance in C++.

Object Orientation

I am going to assume that the use of inheritance in the object oriented way is sufficiently known, so I won’t go into details about the Liskov Substitution Principle and other general aspects of that topic. There are however a few aspects that are different to other languages like C# or Java, so I will briefly address them here.

Virtual Functions in Constructor and Destructor

I assume you know about virtual functions and how they work in general. However for many C++ developers the use of virtual functions in constructors and destructors holds a surprise. Consider the following code:

struct Base {
  Base() {
    foo();
  }
  virtual void foo() {
    std::cout << "Base::foo\n";
  }
};

struct Derived : public Base {
  virtual void foo() override {
    std::cout << "Derived::foo\n";
  }
};

int main() {
  Derived d; //prints Base::foo
}


Many would expect the string “Derived::foo” to be printed, since it is a `Derived` object that gets created. But if you know what the compiler does when constructing the object, it becomes obvious why that does not happen:

When constructing a `Derived` object, the very first thing is the construction of the `Base` part of that object. The `Derived` object has not yet started to exist. The identity of the whole thing as a `Derived` object gets established after all parts of it, including the `Base` subobject have been initialized. So when `foo()` gets called, there is nothing but a `Base` object, and therefore the only `foo` that can get called is `Base::foo`.

The same reasoning applies for calls of virtual functions in destructors: The `Base` destructor gets executed as last step after all other parts of the `Derived` object have been destroyed, and then the identity of the object as a `Derived` has already ended.

Virtual Destructors

The `Base` class above is not properly written. In accord with this rule by Herb Sutter, the destructor of `Base` should be either virtual or protected. Virtual if you want to be able to call `delete` on `Base` class pointers, including use of `Base` smart pointers, protected otherwise.

Since object oriented inheritance usually goes hand in hand with some kind of base class pointers, making the destructor public and virtual is the correct choice here. Therefore, taking into account the Rule of All or Nothing, `Base` should look like this:

struct Base {
  Base() {
    foo();
  }

  virtual ~Base = default;
  Base(Base const&) = default;
  Base(Base&&) = default;
  Base& operator=(Base const&) = default;
  Base& operator=(Base&&) = default;

  virtual void foo() {
    std::cout << "Base::foo\n";
  }
};

This looks like ahuge overhead, but it is sufficient to declare the virtual destructor in the topmost base class, any derived class’ destructors will be automatically virtual, too.

Multiple Inheritance

Other than many other languages that allow classes to be derived from only a single other class and possibly implement one or more interfaces, C++ allows real multiple inheritance. That means, a class is allowed to derive from more than one full grown class which can each have their own member variables, virtual and nonvirtual functions and so on.

This can lead to several problems, some of them very subtle and counterintuitive. For example, if two base classes have a function with the same name, calling one of them in the derived class can be ambiguous. If two base classes derive from the same class, a “Deadly Diamond of Death” is formed, which is the reason why C++ has virtual inheritance.

Inheritance as a technical tool

When a class is derived from another, it not only inherits the member variables and methods but also any typedefs and static members of that class. This can be used in different occasions.

Template Metaprogramming

A widely used technique in C++, especially among library writers, is template metaprogramming. It often involves small classes that consist of nothing more than typedefs and constant definitions. Often those classes never get instantiated, i.e. no objects of them are created.

Many classes that are used in template metaprogramming derive from each other to leverage the inheritance of constants and typedefs from their base classes instead of having to redefine them. Examples for such classes is the template `std::integral_constant`. And two of it’s instantiations, `std::true_type` and `std::false_type` It contains two typedefs and a static constant value, in this case `true` and `false` respectively.

A very short example for template metafunctions is a little template that determines whether an unsigned int is even or odd:

template <unsigned int N>
struct is_odd : std::integral_constant<bool, N%2>
{};

template <unsigned int N>
struct is_even : std::integral_constant<bool, !is_odd<N>::value>
{};

Deriving From Common Implementation Details

Sometimes several classes share some common details of their implementation. It is normal to factor that implementation out into another class that is used by all of those classes. The usual choice would be to make an object of the utility class a private member of each class, but there may be reasons an implementer chooses to rather derive from the utility class:

  • If the utility class provides many methods of the derived class’ interface the derived class would have to implement forwarding functions.
  • If the utility class is not maintained by the implementer of the derived class and contains protected functions the implementer needs access to.
  • Empty Base Class Optimization. If the utility class has no nonstatic members and no virtual functions, it contains no real data, i.e. does not need to occupy any space. For different reasons, a member of a class always needs to occupy a few bytes, but a base class does not need that. Therefore, many compilers optimize the space occupied by an empty base class away, making the object effectively smaller. Of course this should only be used if the need for such an optimization is given. (Note: see the comments below how to use EBO on your member variables instead of the class itself).

The Curiously Recurring Template Pattern

The CRTP is another example of templates and inheritance working together in C++. It means that a class derives from a template that has been instantiated with just the deriving class:

template <class D>
class Base {
  //...
};

class Derived : public Base<Derived> {
  //...
};

One of the most often seen occasions where this trick is use is static polymorphism: The base class can call methods of the derived class without having to define virtual prototypes for them. The invocations of those methods can be a bit faster that virtual function calls. However, two derived classes do not have a common base class, since they derive from two different instantiations of the template, which are different classes.

Conclusion

There are many ways in which inheritance can be used in C++, and many of them do not imply an object oriented “Is-A” relationship. The ones that do can usually be identified by the public virtual destructor in the topmost base classes.

Previous Post
Next Post
Posted in

6 Comments



  1. OT: I can’t click the “prev” and “next” links of every articals from mobile version Chrome.

    Reply

    1. As far as I can see, the link you provided states that empty members can not have zero size because the standard forbids it. It then shows how to make use of EBO, by factoring the empty members out into base classes.

      That is exactly my point in the section where I mention EBO.

      Reply

      1. Sure it’s about EBO. Wrapping it with a member avoids polluting the inheritance chain.

        The take away is: sure you can inherit privately, but if you have members you can avoid polluting the inheritance chain as explained in that article

        Reply

        1. Okay, now I get your point. I’ll add a note in the post.

          Reply

Leave a Reply

Your email address will not be published. Required fields are marked *