Hello! Welcome to the next lesson in our journey through C++.
In our previous lesson, we saw how virtual functions and virtual destructors are essential for managing polymorphic objects correctly, allowing derived classes to optionally override base class behavior and ensuring proper cleanup. This enables a great deal of flexibility.
However, sometimes we need more than just flexibility; we need a guarantee. What if we want to define a "contract" that says any class inheriting from our base class must provide its own implementation for a certain function? This is particularly useful when a base class represents an abstract concept for which a default implementation doesn't make sense.
Today, you will learn how to enforce such contracts by defining and using abstract base classes with pure virtual functions. This is the C++ way of creating what are known in other languages, like Java or TypeScript, as "interfaces."
1. From Optional Overrides to a Mandatory Contract
Recall that a regular virtual function provides an implementation that derived classes can, but are not required to, override.
class Entity {
public:
// A derived class CAN override this, but doesn't HAVE to.
virtual std::string GetName() { return "Entity"; }
};
But what if this default behavior is meaningless? What is the "name" of a generic Entity? It makes more sense to force specialized classes like Player or Enemy to provide their own names.
To do this, we declare the function as a pure virtual function. The syntax is to replace the function body with = 0;.
class Entity {
public:
// This function MUST be implemented by a concrete derived class.
virtual std::string GetName() = 0;
};
Two crucial things happen when you add a pure virtual function to a class:
- The function has no implementation in the base class.
- The class itself becomes an abstract base class.
The most important consequence of a class being abstract is that you cannot create an instance of it. The compiler will stop you. An abstract class is an incomplete blueprint, intended only to be a base for other, more concrete classes.
Let's see this in action.
Interfaces in C++ (Pure Virtual Functions)
This video from The Cherno clearly explains the motivation for pure virtual functions and demonstrates how they make a class abstract.
Please watch from the beginning to 02:46. The video will first explain the concept of a pure virtual function, then motivate why it's needed over a regular virtual function, and finally show the = 0 syntax and the resulting compiler error when you try to instantiate an abstract class.
2. Interfaces in C++
Since you have experience with Java and TypeScript, you're likely familiar with the interface keyword. It's used to define a pure contract—a set of method signatures that a class promises to implement.
C++ does not have an interface keyword. Instead, the concept of an interface is a design pattern achieved using an abstract base class that (typically) contains only pure virtual functions and no data members.
When should I use an interface and when should I use a base class?
This resource from DesignGurus.io explains how interfaces are handled in different languages. Since you know some of these, it will help you map the C++ concept to what you already understand.
Read the subsection titled 'C++', which starts with 'No Formal Interfaces'. It provides a concise explanation and a clear C++ code example of a Drawable interface.
The key idea is that we use an abstract class to define a "can-do" relationship. An object that inherits from a Drawable interface "can be drawn." An object inheriting from a Serializable interface "can be serialized." This is a powerful tool for designing flexible systems.
For another perspective, let's look at an example that might resonate with your interests in engineering and game development.
Classes part 19 - Interfaces in C++ (Pure virtual functions) | Modern Cpp Series Ep. 55
In this video, Mike Shah introduces the concept using an excellent IRenderer example, a common pattern in graphics programming. The 'I' prefix is a popular convention for identifying interface classes.
Watch the first two minutes (00:00 - 02:05). He explains how an IRenderer interface allows a program to swap out different rendering backends (like OpenGL or Vulkan) without changing the rest of the application code.
3. Fulfilling the Contract: Concrete Classes
If a class inherits from an abstract base class, it must provide implementations for all of the pure virtual functions it inherits. Once it does, it becomes a concrete class, and you can create instances of it.
What happens if the derived class fails to implement one of the pure virtual functions? The "abstract" property is inherited, and the derived class is also considered abstract.

This shows how the compiler enforces the contract. To be "concrete," you must fulfill all the promises made by your base classes.
Let's see this demonstrated.
Classes part 19 - Interfaces in C++ (Pure virtual functions) | Modern Cpp Series Ep. 55
Let's return to Mike Shah's video. He will now make multiple functions in his IRenderer interface pure virtual and show how derived classes must implement all of them.
Watch from 07:42 to 10:04. Notice how the OpenGL class fails to compile when it only implements draw() but not the newly-required update() function. This perfectly demonstrates the all-or-nothing nature of the contract.
4. A Powerful Use Case: Writing Generic, Decoupled Code
The real power of interfaces comes from writing code that operates on the abstract base type. This decouples the code that uses an object from the concrete implementation of that object.
A classic anti-pattern in programming is a long if-else-if chain that checks the type of an object and behaves differently for each type. This is fragile; when you add a new type, you have to find and update all these checks.
Abstract classes and pure virtual functions solve this elegantly.
virtual functions - Standard C++
The C++ Core Guidelines FAQ provides an excellent, practical example of refactoring away from this 'else-if-heimer's disease' by introducing a pure virtual function.
Find the section starting with 'The problem with this is what I call “else-if-heimer’s disease”' and read to the end of that FAQ entry. This demonstrates how to replace a type-checking if block with a single polymorphic call to a virtual function, leading to much cleaner and more maintainable code.
By defining a fooBar() function in the Vehicle interface, the myCode function no longer needs to know about Cars or Trucks. It just needs to know that it has a list of Vehicles, and every Vehicle knows how to fooBar() itself. When you add a Motorcycle class later, the myCode function doesn't need to change at all.
This powerful idea allows you to build systems where components interact through well-defined contracts (interfaces), without needing to know about each other's internal details.
Conclusion
Today we've bridged the gap between optional polymorphism and enforced contracts. You've learned how to create abstract base classes that define what a derived class must do, a cornerstone of object-oriented design.
Key Takeaways:
- A pure virtual function is declared with
= 0;and has no implementation in the base class. - A class with one or more pure virtual functions becomes an abstract base class and cannot be instantiated directly.
- In C++, an interface is a design pattern, typically an abstract class consisting only of pure virtual functions.
- A derived class must
overrideall inherited pure virtual functions to become a concrete class that can be instantiated. - This mechanism allows you to define contracts and write generic code that is decoupled from concrete implementations, avoiding fragile type-checking.
Next Up
We've now built a solid foundation for creating and using polymorphic class hierarchies. Our generic code can work with a Vehicle* without knowing if it's a Car or a Truck. But what if, in a specific situation, we have a Vehicle* and we need to know if it's a Car so we can call a car-specific function (like retractRoof())? How can we safely check the object's true type?
In the next lesson, we will cover how to use dynamic_cast for safe downcasting in a polymorphic class hierarchy, giving you a tool to inspect the dynamic type of an object when necessary.