Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Inheriting from Multiple Base Classes
Understand multiple inheritance to create hierarchical relationships between classes.
Multiple Inheritance
So far, all of the examples of inheritance we've presented have been single inheritance -- that is, each inherited class has one and only one parent. However, C++ provides the ability to do multiple inheritance. Multiple inheritance enables a derived class to inherit members from more than one parent.
Let's say we wanted to write a program to keep track of hybrid vehicles. A hybrid vehicle is electric and also uses fuel. Multiple inheritance can be used to create a HybridCar class that inherits properties from both ElectricVehicle and FuelVehicle. To use multiple inheritance, simply specify each base class (just like in single inheritance), separated by a comma.
#include <string>
#include <string_view>
class ElectricVehicle
{
private:
int m_batteryCapacity{};
int m_chargeLevel{};
public:
ElectricVehicle(int capacity, int charge)
: m_batteryCapacity{ capacity }, m_chargeLevel{ charge }
{
}
int getBatteryCapacity() const { return m_batteryCapacity; }
int getChargeLevel() const { return m_chargeLevel; }
};
class FuelVehicle
{
private:
int m_tankSize{};
double m_fuelLevel{};
public:
FuelVehicle(int tank, double fuel)
: m_tankSize{ tank }, m_fuelLevel{ fuel }
{
}
int getTankSize() const { return m_tankSize; }
double getFuelLevel() const { return m_fuelLevel; }
};
// HybridCar publicly inherits ElectricVehicle and FuelVehicle
class HybridCar : public ElectricVehicle, public FuelVehicle
{
private:
std::string m_model{};
public:
HybridCar(std::string_view model, int battery, int charge, int tank, double fuel)
: ElectricVehicle{ battery, charge }, FuelVehicle{ tank, fuel }, m_model{ model }
{
}
};
int main()
{
HybridCar prius{ "Prius", 100, 80, 50, 35.5 };
return 0;
}
Mixins
A mixin (also spelled "mix-in") is a small class that can be inherited from in order to add properties to a class. The name mixin indicates that the class is intended to be mixed into other classes, not instantiated on its own.
In the following example, the Movable, Collidable, and Renderable classes are mixins that we inherit from in order to create a new GameObject class.
// h/t to reader Waldo for this example
#include <string>
struct Vector2D
{
float x{};
float y{};
};
class Movable // mixin Movable class
{
public:
void setVelocity(float vx, float vy) { m_velocity = {vx, vy}; }
void setPosition(float px, float py) { m_position = {px, py}; }
private:
Vector2D m_velocity{};
Vector2D m_position{};
};
class Collidable // mixin Collidable class
{
public:
void setCollisionRadius(float radius) { m_radius = radius; }
void setCollisionEnabled(bool enabled) { m_collisionEnabled = enabled; }
private:
float m_radius{};
bool m_collisionEnabled{};
};
class Renderable // mixin Renderable class
{
public:
void setVisible(bool visible) { m_visible = visible; }
private:
bool m_visible{};
};
class GameObject : public Movable, public Collidable, public Renderable {}; // GameObject using three mixins
int main()
{
GameObject player{};
player.Movable::setPosition(10.0f, 20.0f);
player.Movable::setVelocity(1.0f, 0.5f);
player.Collidable::setCollisionRadius(5.0f);
player.Collidable::setCollisionEnabled(true);
player.Renderable::setVisible(true);
}
You may be wondering why we use explicit Movable::, Collidable::, and Renderable:: scope resolution prefixes when this isn't necessary in most cases.
Collidable::setCollisionEnabled()and other functions could potentially have name collisions if mixins had functions with the same signature. Using prefixes avoids ambiguity.- In non-ambiguous cases, using the mixin name provides documentation as to which mixin the function call applies to, which helps make our code easier to understand.
- Non-ambiguous cases may become ambiguous in the future if we add additional mixins. Using explicit prefixes helps prevent this from occurring.
Advanced note: Because mixins are designed to add functionality to the derived class, not to provide an interface, mixins typically do not use virtual functions (covered in the next chapter). Instead, if a mixin class needs to be customized to work in a particular way, templates are typically used. For this reason, mixin classes are often templatized. Perhaps surprisingly, a derived class can inherit from a mixin base class using the derived class as a template type parameter. Such inheritance is called Curiously Recurring Template Pattern (CRTP for short), which looks like this:
// The Curiously Recurring Template Pattern (CRTP)
template <class T>
class Mixin
{
// Mixin<T> can use template type parameter T to access members of Derived
// via (static_cast<T*>(this))
};
class Derived : public Mixin<Derived>
{
};
You can find a simple example using CRTP here.
Problems with multiple inheritance
While multiple inheritance seems like a simple extension of single inheritance, multiple inheritance introduces a lot of issues that can markedly increase the complexity of programs and make them a maintenance nightmare. Let's take a look at some of these situations.
First, ambiguity can result when multiple base classes contain a function with the same name. For example:
#include <iostream>
class InputHandler
{
private:
int m_id{};
public:
InputHandler(int id)
: m_id{ id }
{
}
int getID() const { return m_id; }
};
class NetworkHandler
{
private:
int m_id{};
public:
NetworkHandler(int id)
: m_id{ id }
{
}
int getID() const { return m_id; }
};
class GameClient: public InputHandler, public NetworkHandler
{
public:
GameClient(int inputID, int networkID)
: InputHandler{ inputID }, NetworkHandler{ networkID }
{
}
};
int main()
{
GameClient client{ 1234, 5678 };
std::cout << client.getID(); // Which getID() do we call?
return 0;
}
When client.getID() is compiled, the compiler looks to see if GameClient contains a function named getID(). It doesn't. The compiler then looks to see if any of the parent classes have a function named getID(). See the problem here? The problem is that client actually contains TWO getID() functions: one inherited from InputHandler, and one inherited from NetworkHandler. Consequently, this function call is ambiguous, and you will receive a compiler error if you try to compile it.
However, there is a way to work around this problem: you can explicitly specify which version you meant to call:
int main()
{
GameClient client{ 1234, 5678 };
std::cout << client.InputHandler::getID();
return 0;
}
While this workaround is pretty simple, you can see how things can get complex when your class inherits from four or six base classes, which inherit from other classes themselves. The potential for naming conflicts increases exponentially as you inherit more classes, and each of these naming conflicts needs to be resolved explicitly.
Second, and more serious is the diamond problem, sometimes called the "diamond of doom". This occurs when a class multiply inherits from two classes which each inherit from a single base class. This leads to a diamond shaped inheritance pattern.
For example, consider the following set of classes:
class Component
{
};
class AudioComponent: public Component
{
};
class GraphicsComponent: public Component
{
};
class RenderAudioVisual: public AudioComponent, public GraphicsComponent
{
};
AudioComponents and GraphicsComponents are both Components, so they derived from Component. However, a RenderAudioVisual incorporates the functionality of both AudioComponents and GraphicsComponents.
There are many issues that arise in this context, including whether RenderAudioVisual should have one or two copies of Component, and how to resolve certain types of ambiguous references. While most of these issues can be addressed through explicit scoping, the maintenance overhead added to your classes in order to deal with the added complexity can cause development time to skyrocket.
Is multiple inheritance more trouble than it's worth?
As it turns out, most of the problems that can be solved using multiple inheritance can be solved using single inheritance as well. Many object-oriented languages (eg. Smalltalk, PHP) do not even support multiple inheritance. Many relatively modern languages such as Java and C# restrict classes to single inheritance of normal classes, but allow multiple inheritance of interface classes (which we will talk about later). The driving idea behind disallowing multiple inheritance in these languages is that it simply makes the language too complex, and ultimately causes more problems than it fixes.
Many experienced programmers believe multiple inheritance in C++ should be avoided at all costs due to the many potential problems it brings. However, there are times and situations when multiple inheritance is the best way to proceed. It should be used extremely judiciously.
As an interesting aside, you have already been using classes written using multiple inheritance without knowing it: the iostream library objects std::cin and std::cout are both implemented using multiple inheritance!
Avoid multiple inheritance unless alternatives lead to more complexity.
Summary
Multiple inheritance enables a derived class to inherit members from more than one parent class by listing multiple base classes separated by commas in the inheritance specification: class Derived : public Base1, public Base2. The derived class inherits all members from all base classes, combining their functionality.
Mixins: A mixin is a small class designed to be inherited from to add specific properties or behaviors to a class. Mixins are intended to be mixed into other classes rather than instantiated directly, providing modular functionality that can be combined as needed. Using explicit scope resolution when calling mixin functions helps avoid ambiguity and provides clear documentation.
Naming conflicts: When multiple base classes contain functions with the same name, calling that function on the derived class creates ambiguity that the compiler cannot resolve. These conflicts must be resolved explicitly using scope resolution: derivedObject.BaseClass::functionName(), which becomes increasingly complex as the number of base classes grows.
Diamond problem: The diamond problem occurs when a class multiply inherits from two classes that both inherit from the same base class, creating a diamond-shaped inheritance pattern. This raises questions about whether the derived class should have one or two copies of the ultimate base class and how to resolve ambiguous references, adding significant complexity and maintenance overhead.
Alternatives to multiple inheritance: Most problems solvable with multiple inheritance can also be solved using single inheritance. Many modern languages restrict classes to single inheritance of normal classes while allowing multiple inheritance of interface classes, driven by the belief that multiple inheritance adds more complexity than value in most scenarios.
When to use multiple inheritance: While many experienced programmers advocate avoiding multiple inheritance entirely due to potential problems, it should be used judiciously when it provides the clearest solution and alternatives would lead to greater complexity. The iostream library demonstrates that multiple inheritance has legitimate uses, as std::cin and std::cout are both implemented using it.
Multiple inheritance provides powerful capabilities for combining functionality from multiple sources but should be approached carefully due to the complexity, ambiguity, and maintenance challenges it introduces.
Inheriting from Multiple Base Classes - Quiz
Test your understanding of the lesson.
Practice Exercises
Smart Device with Multiple Inheritance
Create a NetworkDevice class with network methods and a PoweredDevice class with power methods. Create a SmartCamera class that inherits from both, demonstrating multiple inheritance.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!