Inheritance allows us to define classes and reuse them by creating other classes based upon them. For instance, we can imagine a class `Animal` that a class `Cat` would inherit from. This allows us to create families of objects that share the same properties. We could imagine a class `Feline` that would inherit from `Animal` and that `Cat` would inherit from, so we don't have to repeat ourselves for everything feline-related. ## Base Class A class that is extended by other classes is a called a base class. They allow us to define hierarchy in notions and graph-like structures through class hierarchies. Meanwhile, classes inheriting from others are called derived classes. Coming back to our example, a `Cat` is a `Feline` and would inherit all its members, attributes and methods alike, except for constructors (more on that [[Inheritance#Constructor Inheritance|below]]). Moreover, functions with `Feline` parameters are also available for a `Cat`. Similarly, you can copy a cat into a feline, but not the other way around: inheritance is directional. ```cpp Feline f; Cat c; void display(const Feline&) { /* ... */ } f = c; // ✅ copies common attributes to both classes from c to f display(c); // ✅ Works ! c = f; // 🚫 Yields an error, inheritence goes the other way ``` > ⚠️ `f = c`, while correct, is a side-effect of a somewhat dangerous feature of C++ called **object slicing**. When we assign a derived object `Cat` to a base object `Feline`, the derived part of the object is sliced off. `Cat` is stripped of all the data and features that makes it unique. Incidentally, this is why passing objects by reference like in the case of the `display` function is so important. Passing a `Cat` object by value into a function that has a `Feline` parameter, this would create a sliced copy of `Cat` into a `Feline`, just like what happened with `f = c`. Classes inheriting from others are also allowed to define member attributes and methods for themselves, of course. Inheritance is **transitive**, meaning it's not restricted to first-order descendants, but to all children, no matter how many classes between them. If `C` inherits from `B` and `B` from `A`, then `C` inherits from `A`, too. This property of classes allows us to build networks of classes. Inheritance works as a semantical documentation of entities and their relationship to one another, while reducing boilerplate work and duplicated code in the process. ## Defining Inheritance Imagine we're building a visualization engine for geometric figures. We could define a class `Figure`, which would hold onto attributes such as the points or the color or the figure, and other classes such as `Rectangle`, `Circle`, `Triangle` that would inherit from this class. All are defined by a collection of points represented as $(x,y)$ linked by lines, which could take the form of an `vector<Points>;` attribute implemented by the class `Figure`. A `Line` could be two points, and a point a single `Point`. To make a class inherit from another, we use the `public` keyword. ```cpp #include <vector> using namespace std; struct Point { double x; double y; }; class Figure { private: vector<Point> points_; }; class Triangle : public Figure { /* ... */ }; ``` ## Protected Members Marking members as `private` ensures they can only be accessed from the methods implemented by the class itself. They will not be accessible by classes that inherited from it. How can we make our visualization engine work if a `Triangle` can't access its own `private points` then? To solve this, we replace the keyword `private` with `protected`. Thus, instead of restricting access to certain members to the class itself, we restrict it to the class itself and all other classes inheriting from it. Let's fix the class `Figure`. ```cpp class Figure { protected: vector<Point> points_; }; ``` Now, `Triangle` can access its own `points` as well as all other `protected` members.. For all class that are not part of this inheritance chain, all `protected` members will be equivalent to `private` members, and therefore inaccessible. However, this access is limited to the class's scope. This means `Triangle` can only access its own member attributes it inherited from `Figure`. But a member method of `Triangle` having a parameter object of type `Figure` could not access its `protected` attributes because `this` doesn't have the same scope. If the parameter had been a `Triangle`, this would not have been problematic. >⚠️ Exposing raw data with `protected` is regarded as anti-pattern in modern C++. This breaks encapsulation, resulting in `Figure` losing all control over its own state, which can now freely be modified by any derived class, potentially breaking invariants (rules) that `Figure` relies on in its methods. Let's fix our example, with a `protected` interface this time. ```cpp class Figure { protected: void addPoint(const Point& p) { points_.push_back(p); } private: vector<Point> points_; }; ``` ## Overriding the Base Class It's possible the members as defined by the parent class are not suitable for the purposes of the class that extends it. Imagine for instance that we had define `Figure` with a protected member method `draw` that works well for all figures that are made of points. What of circles? We need to redefine their method draw, even though they're still figures. So, some `protected` members or attributes might need to be redefined in certain instances. To achieve this, no need to redefine the inheritance chain. We need only to shadow the parent class's members. This is especially useful for methods, when one default implementation suits most situations, but needs to be adapted for specific, specialized use cases. ### Shadowing Shadowing is what happens when a derived class introduces a member whose name hides another in the base class. While this allows one to implement specialized behavior, this is often unintentional and problematic. Consider the following scenario: The class A has a `protected` attribute `a`, and the class `B` inherits from `A` but shadows its attribute `a`. Now we end up in a situation where the member methods in class `B` will use the shadowed value of `a` as redefined. However, because of **scope resolution**, member methods from class `A` will continue to rely on `a`'s initial value. Following that logic, if you have a pointer to a `Figure` that's actually a `Circle`, calling `draw` will call the wrong method. ```cpp Figure* f = new Circle(); f->draw(); // 🚫 Calls Figure::draw(), not Circle::draw(). ``` That's why shadowing is generally frowned upon and needs to be avoided where possible. Once shadowed, members continue to exist and can be accessed through the scope resolution operator. ```cpp class Square : public Figure { void draw() { /* Using the scope resolution operator, we can access shadowed member of the base class */ Figure::draw(); } } ``` ### Overriding Those problems can be fixed with overriding, using the `virtual` and `override` keywords. It acts as intentional and explicit shadowing. By marking the base class functions as `virtual`, we tell the compiler to use **dynamic dispatch**, which means the right function to call will be determined at runtime based on the actual type of the object. ```cpp #include <vector> using namespace std; struct Point {...}; class Figure { // declare the method as virtual virtual void draw() const { /* ... */ } }; class Triangle : public Figure { /* ... */ }; class Square : public Figure { /* ... */ }; class Line : public Figure { /* ... */ }; class Circle: public Figure { public: // override the base class version void draw() const override { /* ... */ } private: Point center_; double radius_; }; int main() { Figure* f = new Circle(); f-> draw(); // ✅ Calls Circle::draw() /* The 'new' keyword manually requests memory from the heap We are now responsible for managing and destroying it Otherwise we create a memory leak */ delete f; return 0; } ``` >👉 The `override` keyword is a modern C++ feature that makes the compiler check that the override of a virtual function is correctly implemented, and that the override is applied to a virtual method. If the base class function signature changes, or if it was never virtual to begin with, using override will cause a compile-time error. This prevents silent, accidental shadowing when you intended to override. This ability to call the correct derived class method through a base class pointer or reference is called **polymorphism**, and is arguably the most powerful feature enabled by inheritance. ### Virtual Destructor We're almost there, but a bug lurks in the program above. When `f` goes out of scope, it's `Figure`'s destructor that will be called: the compiler only knows `f` is a `Figure`. The `Circle` part of the object will never be disposed of, leading to memory leaks. To avoid this, we need to give a `virtual` destructor to a base class that has a `virtual` method. ```cpp class Figure { public: virtual ~Figure() = default; virtual void draw() const { /* ... */ } }; ``` This way, we ensure a dynamic dispatch will be performed to call the correct derived destructor `~Circle()` first, before automatically calling the base destructor `~Figure()`. ## Abstract Classes From our example, we can see it doesn't make much sense to have a generic implementation of draw, depending on what needs to be drawn. But we'd still like all of our derived classes to have this method, because other parts of our program rely on the fact a figure can be drawn. To achieve this, we use an **abstract base** class using a pure virtual function. This is done by replacing the function's body with `= 0;`. ```cpp class Figure { public: virtual ~Figure() = default; virtual void draw() const = 0; }; ``` This is now an abstract class, which has two consequences. First, you can no longer create instances of it directly, which makes perfect sense for our example. ```cpp Figure f = new Circle(); // ✅ Still works Figure f; // 🚫 Compile-time error ``` Second, it forces all concrete (as opposed to abstract) classes to implement a `draw()` method, which is also what we're aiming for. If a `Triangle` inherits from `Figure` but fails to provide its own `override` for `draw()`, it too will be considered to be an abstract class, and you won't be able to create instances of it. This way we can define pure interfaces that all derived classes must conform to. ## Constructors When a class `Circle` inherits from a class `Figure`, it's `Circle`'s responsibility to initialize `Figure`'s attributes within its own constructor. However, `Figure`'s member attributes might be `private`. To circumvent this, we use `Figure`'s constructor within `Circle`'s constructor. Its invocation is done at the beginning of initialization lists. ```cpp class Figure { public: Figure(std::string type) : type_(type) {} private: std::string type_; }; class Circle : public Figure { public: Circle(Point center, double radius, std::string type) : Figure(type), center_(center) radius_(radius) {} private: Point center_; double radius_; }; ``` This is an explicit call to `Figure`'s constructor. It is not necessary, however, to call default constructors. The reverse is also true: If there are no default constructors, we must explicitly call parent constructors in derived classes, otherwise our code won't compile. This hold all along the derivation chain. If `C` inherits from `B`, which in turn inherits from `A`, when we create an object `C`, `B`'s constructor is called, which calls in turn `A`'s constructor before initializing itself. Much like with [[Recursion|recursion]], each function call is pushed onto the stack until there are no more constructor to call; constructors then start returning until we reach the first one that was called. To better understand this Last-In, First-Out process (LIFO), think of Russian dolls. If the biggest doll `C` contains `B` that in turn contains `A`, it stands to reason that you would need to put `A` inside of `B` before putting `B` inside of `C`. Class inheritance works very much like Russian dolls. This is why it's important that calls to base classes' constructors are performed before anything else in initialization lists. >👉 Destructors, on the other hand, are always called in the exact reverse order from that of constructors. Going back to our Russian dolls analogy, you can see how this makes sense: You'd need to open up and dispose of `C` before disposing of `B` before disposing of `A`. ### Copy Constructor If you need to define your own version of the copy constructor for a derived class, you need to call the base class's copy constructor explicitly. Otherwise, the default constructor will be called instead. This can lead to problematic situations, like the copy being initialized with the wrong attribute values for the base class. It gets even messier if the derived class shadows the base class's private attributes. ```cpp class A { public: A(): foo_("bar") {} A(std::string str): foo_(str); void print() const { std::cout << foo_ << std::endl; } private: std::string foo_; }; class B : public A { public: B(): A("baz"), foo_("baz") {} B(std::string str): A(str), foo_(str); B(const B& other): foo_(other.foo) {} // 🚫 Missing A's copy constructor void print() const { std::cout << foo_ << std::endl; } void print_base_value() const { A::print(); } private: std::string foo_; }; int main() { B b; b.print(); // ✅ "baz" b.print_base_value(); // ✅ "baz" B c(b); c.print(); // ✅ "baz" c.print_base_value(); // 🚫 "bar" return 0; } ``` To fix this, we need to update the copy constructor to call `A`'s own copy constructor. ```cpp B(const B& other): A(other), foo_(other.foo) {} ``` ### Constructor Inheritance As mentioned earlier, derived classes don't inherit constructors from base classes. C++ 11, however, makes this possible on an opt-in basis. You can ask for constructor inheritance with the keyword `using`. This way you can inherit all constructors from the base class. ```cpp class A { public: A(int x); A(double x, double y); }; class B : public A { using A::A; // A's constructors are now availabe on B } ``` Those constructors, however, don't initialize specific attributes of the derived class. You would then need to define constructors with new signatures so that you can call those derived constructors and initialize the derived class attributes. This is error-prone, which is why it's recommended to stick to using `using` when the derived class doesn't have its own attributes. It's one of those cases where, if you're not sure you need it, you probably don't. It's mostly useful to know in case you come across it in a repo.