C++ Inheritance
C++ Inheritance: Building Hierarchies
Section titled “C++ Inheritance: Building Hierarchies”Inheritance is a fundamental pillar of Object-Oriented Programming (OOP). It allows a new class (the derived class) to be based on an existing class (the base class). The derived class inherits properties (data members) and behaviors (member functions) from the base class, promoting code reuse, reducing redundancy, and creating logical “is-a” relationships.
Instead of starting from scratch, you can inherit members from a base class and then add new members or modify inherited ones in the derived class. This establishes a hierarchy. For example, a Dog is a Mammal, and a Mammal is an Animal. Inheritance models these relationships directly in code.
Base and Derived Classes
Section titled “Base and Derived Classes”A derived class specifies its base class(es) using a derivation list.
class DerivedClassName : accessSpecifier BaseClassName { // members of the derived class};Here, accessSpecifier can be public, protected, or private. If omitted, it defaults to private for classes (defined with class) and public for structs (defined with struct). public inheritance is the most common type and models the “is-a” relationship.
Let’s look at an example with a Shape base class and a Rectangle derived class:
#include <iostream>
// Base classclass Shape {public: // Constructor Shape(int w = 0, int h = 0) : width(w), height(h) {}
void setWidth(int w) { width = w; }
void setHeight(int h) { height = h; }
protected: // Protected members are accessible by derived classes int width; int height;};
// Derived class using public inheritanceclass Rectangle : public Shape {public: // Constructor calling the base class constructor Rectangle(int w = 0, int h = 0) : Shape(w, h) {}
int getArea() const { // Mark method as const as it doesn't modify the object return (width * height); // Accessing protected members from Shape }};
int main() { Rectangle rect(5, 7);
std::cout << "Initial Area: " << rect.getArea() << std::endl;
rect.setWidth(10); // Accessing public method inherited from Shape
std::cout << "Updated Area: " << rect.getArea() << std::endl;
return 0;}When compiled and executed, this code produces:
Initial Area: 35Updated Area: 70Access Control and Inheritance
Section titled “Access Control and Inheritance”How base class members are inherited depends on the accessSpecifier used during derivation:
- Public Inheritance (
public):publicmembers of the base class remainpublicin the derived class.protectedmembers of the base class remainprotectedin the derived class.privatemembers of the base class are inherited but are inaccessible directly by the derived class (though they can be accessed indirectly via public or protected base class methods). This is the most common type and represents an “is-a” relationship. - Protected Inheritance (
protected): Bothpublicandprotectedmembers of the base class becomeprotectedin the derived class.privatemembers remain inaccessible. - Private Inheritance (
private): Bothpublicandprotectedmembers of the base class becomeprivatein the derived class.privatemembers remain inaccessible. This models an “is-implemented-in-terms-of” relationship rather than “is-a”.
Here’s a summary of access levels:
| Access From | public Member | protected Member | private Member |
|---|---|---|---|
| Same Class | Yes | Yes | Yes |
| Derived Classes (via Public Inheritance) | Yes | Yes | No |
| Outside Classes (Objects) | Yes | No | No |
Important points about inheritance:
- Constructors and destructors are not inherited, but derived class constructors automatically call the base class’s default constructor unless another base constructor is specified in the initializer list.
- Assignment operators (
operator=) are not inherited. - Friend functions and friend classes of the base class are not inherited.
- It’s crucial to declare the base class destructor
virtualif you plan to delete derived objects through base class pointers, to prevent slicing and resource leaks. Example:virtual ~Shape() = default;
Overriding Methods and the override Keyword
Section titled “Overriding Methods and the override Keyword”A derived class can provide its own implementation of a member function inherited from a base class. This is called overriding. For virtual functions in the base class, using the override specifier in the derived class is highly recommended. It helps the compiler verify that you are indeed overriding a base class virtual function and haven’t made a signature mistake.
class Base {public: virtual void print() const { std::cout << "Base\n"; } virtual ~Base() = default; // Virtual destructor};
class Derived : public Base {public: // Correctly overrides Base::print void print() const override { std::cout << "Derived\n"; }};The final Specifier
Section titled “The final Specifier”You can prevent a class from being inherited further by marking it as final. You can also prevent a specific virtual function from being overridden further down the hierarchy by marking it final.
class CannotDerive final : public Base { /* ... */ };
class BaseWithFinalMethod {public: virtual void cannotOverride() final { /* ... */ } virtual ~BaseWithFinalMethod() = default;};Multiple Inheritance
Section titled “Multiple Inheritance”C++ allows a derived class to inherit from multiple base classes.
class DerivedClassName : accessSpecifier BaseA, accessSpecifier BaseB { // ...};Consider a Rectangle that inherits properties from Shape and costs from PaintCost:
#include <iostream>
// Base class 1class Shape {public: Shape(int w = 0, int h = 0) : width(w), height(h) {} virtual ~Shape() = default; // Virtual destructor
void setWidth(int w) { width = w; } void setHeight(int h) { height = h; }
protected: int width; int height;};
// Base class 2class PaintCost {public: virtual ~PaintCost() = default; // Virtual destructor int getCost(int area) const { return area * 70; // Cost per unit area }};
// Derived class inheriting from bothclass PaintedRectangle : public Shape, public PaintCost {public: PaintedRectangle(int w = 0, int h = 0) : Shape(w, h) {}
int getArea() const { return (width * height); }};
int main() { PaintedRectangle rect(5, 7); int area = rect.getArea();
std::cout << "Total area: " << area << std::endl; std::cout << "Total paint cost: $" << rect.getCost(area) << std::endl;
return 0;}Output:
Total area: 35Total paint cost: $2450Caution: Multiple inheritance can lead to the “Diamond Problem” if two base classes inherit from a common grandparent class. This ambiguity needs careful resolution, often using virtual inheritance (class BaseA : virtual public Grandparent). While powerful, multiple inheritance should be used judiciously.