Classes can inherit from each other with [[Inheritance|inheritance]]. They can not only inherit from another class, but they can inherit from multiple other classes. This is mostly useful for defining *shared behavior* or *traits*, as the Rust language calls it. For instance, in the physical world, there are waves and there are particles. And then there is light, which is both, depending on how you measure it. It wouldn't make sense to group all behaviors from waves and particles into a single class, because then we'd force all descendent of this class to implement all methods for both waves and particles, even though most of them would be either one or the other. On the other hand, it wouldn't make much sense either to define a third class `WaveParticle` from which `Light` could inherit, because this would cause us to duplicate all of our code. The solution to this design problem is to create two classes, `Wave` and `Particle`, and let `Light` inherit from both. This way, light inherits both types and attributes and can be treated as one or the other depending on the context. ```cpp class Wave {}; class Particle {}; class Light: public Wave, public Particle {}; ``` Polymorphism would then allow us to store a `Light` object in a `std::vector<std::unique_ptr<Wave>>` or to pass it as an argument to a `void display(const Particle&)` function. This way we avoid forcing all `Particle` objects to implement dummy versions of the `Wave` template, and the other way around, and we didn't duplicate any code. This is ideal, but raises two questions: 1. What happens when two inherited classes define methods with identical names? 2. What happens when a class inherits from two classes that inherit from the same base class? ## Competing Inherited Member Methods When a class inherits from multiple base classes that have member functions with the exact same name and signature, the compiler doesn't pick a 'winner'. Instead, a call to that function from a derived class object results in a compile-time ambiguity error. You can't know which function is called, and neither can the compiler. The first solution to that issue is leveraging the scope resolution operator to remove the ambiguity. Imagining the `Wave` class has a `void display() const` member, we write the following: ```cpp int main() { Light ray; ray.Wave::display(); return 0; } ``` This solves our immediate problem, which is the compiler error. But we don't solve the design issue, which is that the consumer must concern himself with the inner workings of our base classes `Wave` and `Particle`, thus breaking encapsulation principles and defeating the purpose of inheritance. A better solution would be for the `Light` class to make that choice with the help of the keyword `using`, specifying which method to bring into scope. ```cpp class Wave {}; class Particle {}; class Light: public Wave, public Particle { using Wave::display; }; ``` We need not to repeat the function's signature. Now we're able to call the display method from a `Light` object. This is, however, still problematic. In some cases, we might want to display light as a particle instead. We can't extend this solution to take the context in which we're displaying light into account. A *functional* approach could be the declaration of two functions, each accepting either a `Wave` or a `Particle` reference, so that we can use polymorphism to our advantage. ```cpp void display_wave(const Wave&) {} void display_particle(const Particle&) {} int main() { Light ray; display_particle(ray); return 0; } ``` Note that the functions need to be named differently, otherwise we end up with the same ambiguity we started with. The *object-oriented* approach is to implement a member method within the `Light` class that either calls both methods, or only the one we need based on a parameter that would provide information about the consumer's context. ```cpp class Wave {}; class Particle {}; class Light: public Wave, public Particle { void display() const { Wave::display(); Particle::display(); } }; ``` This is often the best solution. The new `Light::display` method resolves the ambiguity for all outside callers, hiding the implementation detail that `Light` is both a `Wave` and a `Particle`, creating a clean, unambiguous interface. ## Order of Inheritance Determines Instantiation Before we move on to tackling our second question, we'll take a quick look at how derived classes are instantiated when there are several base classes. The first thing we need to understand is that the order of inheritance matters. It determines the order in which constructors and destructors are called. Constructors are called in the order of declared inheritance. That is the case even if we explicitly call base classes' constructors in the initialization list of the derived class's constructor in a different one. >👉 Because this can be quite confusing, modern compilers yield a warning when such mismatches occur. To avoid confusion, the best practice is to write both calls to constructors in the initialization list and declarations of inheritance in the same order. While defaults constructors need not to be explicitly called, we still must initialize inherited attributes in the derived class's initialization list by calling base classes' constructors and forwarding them the appropriate arguments. ```cpp class A { public: A(int i) { std::cout << "A "; } }; class B { public: B(int i) { std::cout << "B "; } }; // Initializer list order: A, then B (mismatched!) // The compiler yields a warning class C : public B, public A { public: C(int i) : A(i), B(i+1) {} }; // Output will be "B(43) A(42)" because B is constructed first. int main() { C c(42); return 0; } ``` Destructors, on the other hand, are always called in the precise reverse order of construction. For class `C`, the `C` destructor will be called first, then `A`'s and, lastly, `B`'s. We now understand how are instantiated base classes of derived classes. Next, we'll see what happens when two classes of the declared inheritance actually both are derived classes of a common base class. ## Diamond-Shaped Class Inheritance A copier is both a scanner and a printer. In the same way it didn't make sense to reimplement waves and particles from scratch for the `Light` class, it wouldn't make sense to define a `Copier` on its own. Instead, we define it in terms of a `Scanner` and a `Printer`, which are both derived classes of `Device`. ```cpp class Device { /* ... */ }; class Scanner: public Device { /* ... */ }; class Printer: public Device { /* ... */ }; class Copier: public Scanner, public Printer { /* ... */ }; int main() { Copier c; return 0; } ``` In this scenario, it should be the role of the `Device` class to attribute a unique identifier or serial number to objects in its constructor. Meanwhile, a `Copier` is a device of its own. As such, it must have a unique identifier or serial number. However, when we create a `Copier` object, both the `Scanner` and `Printer` classes will call the `Device` constructor before they can call their own. They have no knowledge of each other, and they both need to be instantiated following the rules of inheritance. It follows that a `Copier` object will contain two instances of the `Device` class when it's instantiated, and therefore two separate unique identifiers. This is problematic. What's more, when we try to access `Device` attributes or methods on a `Copier` object, we can't know whether we're trying to access the `Device` instance contained by the `Scanner`, or that contained by the `Printer`. The compiler can't resolve this ambiguity either. Because of this, the following results in a compile-time error. ```cpp class Device { /* ... */ }; class Scanner: public Device { /* ... */ }; class Printer: public Device { /* ... */ }; class Copier: public Scanner, public Printer { /* ... */ }; int main() { Copier c; // 🚫 Compile-time error: 'id' is ambiguous std::cout << c.id << std::endl; return 0; } ``` We can get rid of the error by manually disambiguating our code and accessing `c.Printer::id` instead. But much like a band-aid on a broken window isn't exactly helpful, this doesn't solve our problem. >👉 This situation is not necessarily a problem in itself, if it makes sense for a given use case. Here, it doesn't. Our `Copier` is still composed of two different `Device` instances, each with their own member methods and attributes. Fortunately, C++ offers a tool to escape this pitfall. ## Virtual Inheritance To avoid the two-base class problem, we mark the relationship between `Scanner` and `Device` as virtual, and do the same for the `Printer` class. Doing so moves the task of instantiating the base class from the first-order derived classes, to the class that inherit from them. This means `Scanner` and `Printer` don't call the `Device` constructor anymore; instead, it becomes `Copier`'s job. ```cpp class Device { /* ... */ }; class Scanner: public virtual Device { /* ... */ }; class Printer: public virtual Device { /* ... */ }; class Copier: public Scanner, public Printer { public: Copier(): Device(), Scanner(), Printer() {} }; int main() { Copier c; // ✅ There is no more ambiguity, as Copier is only one device std::cout << c.id << std::endl; return 0; } ``` We've now solved the problem; a `Copier` is a single `Device`, with a single unique identifier. But `Scanner` and `Printer` still can be instantiated on their own, too: The delegation of instantiation of the ultimate base class is *context-dependent*. A virtual inheritance link means that it is the responsibility of the **most derived constructor** to instantiate the *ultimate base class*. Unlike the character of virtual member methods, this virtual inheritance link isn't *transitive*. This means that delegation of instantiation only occurs for directly derived classes of classes having a virtual inheritance link to a common base class. It does not propagate down the inheritance chain without having been redeclared as such. This is the case because the virtual character is not a property of the class itself, but of the relationship between two classes. But here again, if the ultimate base class has a default constructor, we do not need to call it explicitly. It will be called automatically from `Copier`'s constructor, creating exactly one instance of `Device`, provided `Scanner` and `Printer` has a virtual inheritance link to `Device`. Virtual inheritance allows us to solve duplicate issues in diamond-shaped inheritance diagrams. Remember that: - Initializing the base class is the responsibility of the most derived class - Constructor calls of the base class in intermediary derived classes are ignored - Constructor call of the base class is invoked first - Constructors are then called in the order of inheritance - Copy constructors follow the same rules - Destructor calls follow the reverse order >👉 As always, there's no such thing as a free lunch and this comes at a cost. Objects using virtual inheritance are slightly larger, and member access can have some overhead. It's a small trade-off to solve the dreaded diamond problem in a clean way. Not creating such diamond-shaped inheritance patterns, if possible, is the most efficient solution of all.