35% complete
Inheritance in C++
Inheritance is a fundamental concept in object-oriented programming that allows a class to inherit properties and behaviors from another class. This powerful mechanism enables code reuse, establishes relationships between classes, and supports polymorphism—one of the core principles of OOP.
What You'll Learn
- How inheritance works in C++ and its benefits
- Creating base and derived classes
- Understanding different types of inheritance (single, multiple, multilevel)
- Access specifiers and their role in inheritance
- Virtual functions and runtime polymorphism
- Abstract classes and pure virtual functions
- Best practices for designing inheritance hierarchies
Basics of Inheritance
In C++, inheritance allows a class (called the derived class or child class) to inherit attributes and methods from another class (called the base class or parent class). The derived class can extend or override the functionality of the base class.
Basic Syntax
The syntax for defining a derived class that inherits from a base class is:
1class BaseClassName {2 // Base class members3};45class DerivedClassName : access-specifier BaseClassName {6 // Derived class members7};
The access-specifier
can be one of the following:
- public: Public and protected members of the base class become public and protected members of the derived class.
- protected: Public and protected members of the base class become protected members of the derived class.
- private: Public and protected members of the base class become private members of the derived class.
Note: If no access specifier is provided, the default is private
for classes and public
for structs.
Simple Inheritance Example
1#include <iostream>2#include <string>3using namespace std;45// Base class6class Animal {7public:8 string name;910 void eat() {11 cout << name << " is eating." << endl;12 }1314 void sleep() {15 cout << name << " is sleeping." << endl;16 }17};1819// Derived class20class Dog : public Animal {21public:22 void bark() {23 cout << name << " is barking." << endl;24 }25};2627int main() {28 Dog myDog;29 myDog.name = "Buddy"; // Accessing base class member3031 myDog.eat(); // Calling base class method32 myDog.sleep(); // Calling base class method33 myDog.bark(); // Calling derived class method3435 return 0;36}
In this example, the Dog
class inherits all the public members (name
, eat()
, and sleep()
) from the Animal
class and adds its own method bark()
.
Types of Inheritance in C++
C++ supports several types of inheritance, providing flexibility in how classes can be structured and related to each other.
1. Single Inheritance
A derived class inherits from only one base class. This is the simplest form of inheritance.
Base Class ↑ Derived Class
2. Multiple Inheritance
A derived class inherits from more than one base class. This allows a class to combine features from multiple sources.
Base Class 1 Base Class 2 ↑ ↑ └─────┬───────┘ ↓ Derived Class
1#include <iostream>2using namespace std;34class Vehicle {5public:6 void drive() {7 cout << "Vehicle is being driven." << endl;8 }9};1011class Entertainment {12public:13 void playMusic() {14 cout << "Playing music." << endl;15 }16};1718// Multiple inheritance19class Car : public Vehicle, public Entertainment {20public:21 void honk() {22 cout << "Car is honking." << endl;23 }24};2526int main() {27 Car myCar;2829 myCar.drive(); // From Vehicle30 myCar.playMusic(); // From Entertainment31 myCar.honk(); // From Car3233 return 0;34}
Warning: Multiple inheritance can lead to the "diamond problem" when a class inherits from two classes that have a common base class. This can be resolved using virtual inheritance.
3. Multilevel Inheritance
A derived class inherits from a base class, which in turn inherits from another base class, forming a chain of inheritance.
Base Class ↑ Intermediate Class ↑ Derived Class
1#include <iostream>2using namespace std;34class Animal {5public:6 void eat() {7 cout << "Animal is eating." << endl;8 }9};1011class Mammal : public Animal {12public:13 void breathe() {14 cout << "Mammal is breathing." << endl;15 }16};1718class Dog : public Mammal {19public:20 void bark() {21 cout << "Dog is barking." << endl;22 }23};2425int main() {26 Dog myDog;2728 myDog.eat(); // From Animal (grandparent)29 myDog.breathe(); // From Mammal (parent)30 myDog.bark(); // From Dog3132 return 0;33}
4. Hierarchical Inheritance
Multiple derived classes inherit from a single base class.
Base Class ↑ ┌───────────┼───────────┐ ↓ ↓ ↓ Derived Class 1 Derived Class 2 Derived Class 3
5. Hybrid Inheritance
A combination of two or more types of inheritance. For example, a combination of multilevel and multiple inheritance.
Constructors and Destructors in Inheritance
When a derived class object is created, the constructors of all base classes are called first, followed by the derived class constructor. When the object is destroyed, destructors are called in reverse order—derived class destructor first, followed by base class destructors.
1#include <iostream>2using namespace std;34class Base {5public:6 Base() {7 cout << "Base constructor called" << endl;8 }910 ~Base() {11 cout << "Base destructor called" << endl;12 }13};1415class Derived : public Base {16public:17 Derived() {18 cout << "Derived constructor called" << endl;19 }2021 ~Derived() {22 cout << "Derived destructor called" << endl;23 }24};2526int main() {27 Derived obj;28 // Output:29 // Base constructor called30 // Derived constructor called3132 // When obj goes out of scope:33 // Derived destructor called34 // Base destructor called3536 return 0;37}
Constructor Initialization in Derived Classes
If a base class requires parameters for its constructor, the derived class must pass these parameters to the base class constructor using an initialization list.
1#include <iostream>2#include <string>3using namespace std;45class Person {6protected:7 string name;8 int age;910public:11 Person(string n, int a) : name(n), age(a) {12 cout << "Person constructor called" << endl;13 }14};1516class Student : public Person {17private:18 int studentId;1920public:21 // Passing parameters to the base class constructor22 Student(string n, int a, int id) : Person(n, a), studentId(id) {23 cout << "Student constructor called" << endl;24 }2526 void display() {27 cout << "Name: " << name << ", Age: " << age << ", ID: " << studentId << endl;28 }29};3031int main() {32 Student s("John", 20, 12345);33 s.display();3435 return 0;36}
Access Control and Inheritance
The accessibility of base class members in the derived class depends on two factors:
- The access specifier of the member in the base class (public, protected, or private)
- The type of inheritance (public, protected, or private)
Base Class Member | Public Inheritance | Protected Inheritance | Private Inheritance |
---|---|---|---|
Public | Public | Protected | Private |
Protected | Protected | Protected | Private |
Private | Not accessible | Not accessible | Not accessible |
Important things to note:
- Private members of a base class are never accessible directly from a derived class.
- Protected members are accessible in derived classes but not from outside the class hierarchy.
- Public inheritance is the most common type, preserving the access levels of the base class.
Polymorphism and Virtual Functions
Polymorphism allows objects of different classes to be treated as objects of a common base class. In C++, runtime polymorphism is achieved using virtual functions.
Virtual Functions
A virtual function is a member function in a base class that is declared using the virtual
keyword. When you override a virtual function in a derived class, and call the function using a base class pointer or reference, the derived class's version of the function is called.
1#include <iostream>2using namespace std;34class Shape {5public:6 virtual void draw() {7 cout << "Drawing a shape" << endl;8 }9};1011class Circle : public Shape {12public:13 void draw() override {14 cout << "Drawing a circle" << endl;15 }16};1718class Rectangle : public Shape {19public:20 void draw() override {21 cout << "Drawing a rectangle" << endl;22 }23};2425int main() {26 Shape* shapes[3];27 shapes[0] = new Shape();28 shapes[1] = new Circle();29 shapes[2] = new Rectangle();3031 for (int i = 0; i < 3; i++) {32 shapes[i]->draw(); // Calls the appropriate draw() method33 }3435 // Clean up36 for (int i = 0; i < 3; i++) {37 delete shapes[i];38 }3940 return 0;41}
C++11 Tip: Use the override
keyword when overriding virtual functions. It helps catch errors if the function signature doesn't match the base class version.
Virtual Destructors
When using polymorphism, it's important to make the base class destructor virtual. This ensures that the correct destructor is called when a derived class object is deleted through a base class pointer.
1#include <iostream>2using namespace std;34class Base {5public:6 Base() {7 cout << "Base constructor" << endl;8 }910 virtual ~Base() {11 cout << "Base destructor" << endl;12 }13};1415class Derived : public Base {16public:17 Derived() {18 cout << "Derived constructor" << endl;19 }2021 ~Derived() override {22 cout << "Derived destructor" << endl;23 }24};2526int main() {27 Base* ptr = new Derived();28 delete ptr; // Will call both Derived and Base destructors2930 return 0;31}
Important: Always make base class destructors virtual when you expect the class to be used polymorphically. Not doing so can lead to memory leaks and undefined behavior.
Abstract Classes and Pure Virtual Functions
An abstract class is a class that cannot be instantiated directly and is designed to be inherited from. In C++, a class becomes abstract when it has at least one pure virtual function.
Pure Virtual Functions
A pure virtual function is a virtual function that has no implementation in the base class and must be implemented by any concrete (non-abstract) derived class. It is declared by using the = 0
syntax.
1#include <iostream>2using namespace std;34// Abstract class5class Animal {6public:7 // Pure virtual function8 virtual void makeSound() = 0;910 // Regular method11 void sleep() {12 cout << "Zzz..." << endl;13 }14};1516// Concrete class17class Dog : public Animal {18public:19 // Implementation of the pure virtual function20 void makeSound() override {21 cout << "Woof!" << endl;22 }23};2425// Concrete class26class Cat : public Animal {27public:28 // Implementation of the pure virtual function29 void makeSound() override {30 cout << "Meow!" << endl;31 }32};3334int main() {35 // Animal animal; // Error: Cannot instantiate abstract class3637 Dog dog;38 Cat cat;3940 dog.makeSound(); // Outputs: Woof!41 dog.sleep(); // Outputs: Zzz...4243 cat.makeSound(); // Outputs: Meow!44 cat.sleep(); // Outputs: Zzz...4546 // Polymorphic behavior47 Animal* animals[2] = {&dog, &cat};48 for (int i = 0; i < 2; i++) {49 animals[i]->makeSound();50 }5152 return 0;53}
Abstract classes are often used to define interfaces that derived classes must implement, ensuring a consistent set of methods across related classes.
Best Practices for Inheritance
Inheritance Design Guidelines
- Use "is-a" relationships: Inheritance should model "is-a" relationships, not "has-a" relationships (use composition for those).
- Prefer public inheritance: Public inheritance is the most common and preserves the interface of the base class.
- Keep inheritance hierarchies shallow: Deep hierarchies can become difficult to understand and maintain.
- Make base class destructors virtual: Always make destructors virtual when a class has virtual functions.
- Use override for clarity: Use the
override
keyword when overriding virtual functions to catch errors at compile time. - Be cautious with multiple inheritance: Multiple inheritance can be powerful but complex; use it judiciously.
- Consider alternatives: Composition is often a better choice than inheritance for reusing functionality.
The Liskov Substitution Principle
The Liskov Substitution Principle (LSP) states that objects of a derived class should be able to replace objects of the base class without affecting the correctness of the program. This is a key principle for designing robust inheritance hierarchies.
Inheritance vs. Composition
While inheritance is powerful, it creates tight coupling between classes. An alternative approach is composition, where a class contains instances of other classes instead of inheriting from them.
Inheritance
- Models "is-a" relationships
- Enables polymorphism
- Reuses code from parent classes
- Creates tight coupling
- Can lead to complex hierarchies
Composition
- Models "has-a" relationships
- Provides more flexibility
- Creates looser coupling
- Easier to change implementation
- Often follows "favor composition over inheritance" principle
A good rule of thumb is "favor composition over inheritance," which suggests using inheritance only when it truly models an "is-a" relationship and there's a clear need for polymorphism.
Conclusion
Inheritance is a powerful feature of C++ that enables code reuse and polymorphic behavior. By understanding the different types of inheritance, access control, virtual functions, and abstract classes, you can design flexible and maintainable class hierarchies.
Remember to follow best practices like preferring public inheritance, making destructors virtual, and considering composition as an alternative when appropriate. With these tools, you can build robust object-oriented programs that effectively model real-world relationships.