Chapter 11 - Notes
Inheritance Recap
Inheritance allows you to create class hierarchies
Public inheritance models an 'is-a' relationship
Polymorphism
Polymorphism is a feature of OOP languages that allows objects of different types to be treated similarly through inheritance. We will focus on subtype polymorphism.
Polymorphic Behaviour
The issue present is that if a user with an instance of a derived class uses a variable of the base class type to invoke an overridden member method, the base class' method is called.
class Base {
void foo() {
std::cout << "I am Base" << std::endl;
}
};
class Derived: public Base {
void foo() {
std::cout << "I am Derived" << std::endl;
}
};
void invokeFoo(Base& x) {
x.foo();
}
int main() {
Derived derived;
invokeFoo(derived); // This prints "I am Base"
}Ideally the overridden member method of the derived class should be invoked despite accessed through a variable of the base class type.
This can be achieved by declaring the function in the base class as a virtual function using the keyword virtual:
class Base {
virtual void foo() {
std::cout << "I am Base" << std::endl;
}
};
class Derived: public Base {
void foo() {
std::cout << "I am Derived" << std::endl;
}
};
void invokeFoo(Base& x) {
x.foo();
}
int main() {
Derived derived;
invokeFoo(derived); // This now prints "I am Derived"
}Virtual Destructors
When the destructor of a base class is not defined as a virtual function (virtual destructor), calling the delete operator using a pointer of type Base* that actually points to an instance of type Derived would only call the destructor of the base class.
Therefore, base class destructors should always be defined as virtual.
Virtual Function Table
At compile time, the compiler knows nothing about the nature of objects that the invokeFoo method will encounter to be able to ensure that the correct function ends up executing at each point in time. Therefore, the correct derived class implementation of to be invoked is a decision made at runtime.
Consider the following class Base that declares 3 virtual functions:
class Base {
public:
virtual void One() {
// ...
}
virtual void Two() {
// ...
}
virtual void Three() {
// ...
}
};And class Derived that overrides all functions from Base except Base::Two():
class Derived: public Base {
public:
void One() {
// ...
}
// Does not override Base::Two()
void Three() {
// ...
}
};The compiler creates a table called the Virtual Function Table (VFT) for every class that implements a virtual function, or a derived class that overrides it.
In this example, both classes Base and Derived get an instance of their own VFT.
The VFT for Base is shared between all instances of Base, and the VFT for Derived is shared between all instances of Derived. In other words, they're similar to a static member variable.
The VFT can be imagined as a static array containing function pointers, each pointing to the virtual function or override of interest.

Each table is comprised of function pointers, each pointing to the available implementation of a virtual function. In the Derived class, functions One() and Three() point to local implementations, but Two() points to the implementation in Base since it has not overridden it.
When a member function is called on a an instance that has a VFT, the compiler ensures a lookup in the VFT and ensures that the correct implementation is invoked.
Querying a Base Type Pointer
C++ allows you to query a base type pointer Base* if it is of type Derived* using the casting operator dynamic_cast, and performing conditional execution based on the result of the query.
This is called runtime type identification (RTTI) and should be avoided if possible as it leads to poor programming practice.
#include <iostream>
struct Base {
// Define a virtual function to make `Base` polymorphic
virtual void hello() {};
};
struct Foo: public Base {};
struct Bar: public Base {};
int main() {
Base* x = new Foo;
if (dynamic_cast<Foo*>(x)) {
std::cout << "x is Foo" << std::endl;
} else {
std::cout << "x is not Foo" << std::endl;
}
if (dynamic_cast<Bar*>(x)) {
std::cout << "x is Bar" << std::endl;
} else {
std::cout << "x is not Bar" << std::endl;
}
delete x;
}Abstract Base Classes (ABCs)
An abstract base class (ABC) is a base class that cannot be instantiated. Such a class serves only one purpose – that is to be derived from.
In C++ there isn't an abstract keyword to declare an abstract base class unlike other languages such as Java:
public abstract class AbstractJavaClass {}Instead, an abstract class is defined by declaring a pure virtual function:
class AbstractCppClass {
public:
virtual void foo() = 0; // Pure virtual function
};The declaration of a pure virtual function tells the compiler that the function needs to be implemented by any classes that derive from the abstract class (unless the derived class should also be an abstract class).
A class that that can be instantiated, that derived from an abstract base class is called a concrete class.
Purpose of ABCs
Abstract base classes allows programmers to enforce that all derived classes have to provide an implementation for its pure virtual function(s).
These functions can then be used as an "interface" of sorts.
class Language {
public:
virtual void greet() = 0; // Pure virtual function
};
class English {
public:
void greet() { std::cout << "Hello" << std::endl }
};
class French {
public:
void greet() { std::cout << "Bonjour" << std::endl }
};
class Spanish {
public:
void greet() { std::cout << "Hola" << std::endl }
};Language is an abstract base class. In this context it does not make sense for it to implement a Language::greet function (what would it return?)
Therefore, we define it as a pure virtual function such that any concrete classes that derive from it have to implement it.
The Diamond Problem
The diamond problem occurs when two base classes of a particular class share a common base class:

In this example, class D inherits from B and C, who share a common base class A.
However, initialising D will call the constructor of A twice. And accessing/setting any member variable of A through D will result in a compilation error.
This is because the reality is closer to this:

Meaning it is ambiguous if setting D::A::x actually refers to D::B::A::x or D::B::A::x.
In nearly all cases you'd expect the relationship to be: D is an B, is a C, and is also an A, and therefore it does not make sense for D to have two instances of A.
The solution is virtual inheritance. If you expect a derived class to be used as a base class, it is a good idea to define its relationship using the keyword virtual:
struct ClassA {
int x;
};
struct ClassB: public virtual ClassA {};
struct ClassC: public virtual ClassA {};
struct ClassD: public ClassB, public ClassC {};
int main() {
ClassD obj;
obj.x = 10; // This is now ok!
}Because of the keyword virtual inheritance used by classes ClassB and ClassC, it is ensured that when these classes are inherited together under ClassD that the common base class ClassA exists only in a single instance.
This solves the ambiguity problem described before.
Enforcing Intention to Override
Consider the following base class with a virtual function:
struct Base {
virtual void foo() {
std::cout << "I am Base" << std::endl;
};
};Now assume that a derived class were to define a function foo() but with a slightly different signature – one using const to denoting that it's a constant function:
struct Derived {
void foo() const {
std::cout << "I am Derived" << std::endl;
};
};This function will not override Base::foo() as the signature is different due to the presence of const. However the program will continue to compile, but will likely result in behaviour different to that expected by the programmer.
We can use the specifier override to express the intend to override a virtual method in the base class.
struct Derived {
void foo() const override {
std::cout << "I am Derived" << std::endl;
};
};This will result in a compilation error as the compiler is unable to find a virtual function in the base class with the same signature.
Thus, override provides us the ability to explicitly express the intention to override a base class function by getting the compiler to check whether:
The base class function is
virtualThe signature of the base class matches the derived class function declared to
override
The programmer can now fix this compilation error by either adding const to the virtual function in the base class, or removing const from the overriding function in the derived class.
Prevent Function Overriding
Recall that using the final specifier as part of a class definition ensures that the class cannot be derived from.
Similarly a virtual function that's declared as final cannot be overridden in a derived class.
class Fish {
public:
virtual void swim() = 0;
}
class Tuna: public Fish {
public:
// Override Fish::swim() and make it final
virtual swim() override final {
std::cout << "Tuna swims!" << std::endl;
}
};
class BluefinTuna final: public Tuna {
public:
// Error: swim() was final in Tuna, cannot override
virtual swim() {
// ...
}
};
// Error: BluefinTuna is final cannot, cannot inherit
// or use as a base class.
class SmallBluefinTuna: public BluefinTuna {
};Virtual Copy Constructors?
Virtual copy constructors would be extremely useful, for example when creating a collection of type Base* whose elements are specialisations of Base, then assigning each element into another array of the same Base* type.
The virtual copy constructor would ensure a deep copy of each specialised class, despite working with type Base*.
Virtual copy constructors are however not possible because the virtual keyword is to do with polymorphic behaviour generated at runtime. Constructors are not polymorphic in nature as constructors can only construct a fixed type, and therefore not allowed in C++.
Workaround
A trivial workaround for this would be defining a clone function which returns a pointer to a copy of the instance.
struct Number {
// Pure virtual function
virtual Number* clone() = 0;
};
struct Integer: public Number {
int* value;
Integer(int number) {
value = new int(number);
}
Number* clone() {
// Create a new `Integer` with the same value
return new Integer(*value);
}
};
struct Decimal: public Number {
double* value;
Decimal(double number) {
value = new double(number);
}
Number* clone() {
// Create a new `Bar` with the same value
return new Decimal(*value);
}
};
int main() {
Number* number = new Integer(3); // or new Decimal(5.0);
Number* number_copy = number->clone();
delete number;
delete number_copy;
}Last updated
Was this helpful?