What is Inheritance in C++?
Inheritance in C++ is a process where one class acquires the properties and behaviors of another class. The class that inherits is the derived class (or subclass). The class it inherits from is the base class (or superclass).
Inheritance offers several key benefits:
- Code Reusability: You write common code once in the base class. Then, derived classes reuse it without rewriting. This saves time and reduces errors.
- Reduced Development Time: Reusing existing code speeds up development. You focus on new features instead of duplicating efforts.
- Easier Maintenance: Changes in the base class apply to all derived classes. This simplifies updates and bug fixes across your program.
- Improved Readability: Code becomes more organized and easier to understand. You see clear relationships between classes.
How to Implement Inheritance in C++
Implementing inheritance involves declaring a derived class from a base class. Here is the basic syntax:
class DerivedClass : access_specifier BaseClass {
// Members of DerivedClass
};
Here, access_specifier
defines how members of the base class are inherited in the derived class. Common specifiers include public, protected, and private.
Access Specifiers in Inheritance
Access specifiers control member visibility in derived classes:
- Public Inheritance: Base class public members become public in the derived class. protected members become protected. private members remain private and are not directly accessible.
- Protected Inheritance: Base class public and protected members become protected in the derived class. private members remain private.
- Private Inheritance: Base class public and protected members become private in the derived class. private members remain private.
Here is a table summarizing access in derived classes:
Base Class Access | Public Inheritance | Protected Inheritance | Private Inheritance |
---|---|---|---|
public | public | protected | private |
protected | protected | protected | private |
private | Not accessible | Not accessible | Not accessible |
Types of Inheritance in C++
C++ supports five main types of inheritance. Each type serves different purposes for class relationships and code organization.
1. Single Inheritance
Single inheritance involves one derived class inheriting from one base class. This is the simplest and most common type of inheritance.
Example:
You have a Vehicle
base class. Then, you create a Car
class that inherits from Vehicle
.
#include <iostream>
// Base class
class Vehicle {
public:
void accelerate() {
std::cout << "Vehicle is accelerating." << std::endl;
}
};
// Derived class
class Car : public Vehicle {
public:
void drive() {
std::cout << "Car is driving." << std::endl;
}
};
int main() {
Car myCar;
myCar.accelerate(); // Inherited from Vehicle
myCar.drive(); // Member of Car
return 0;
}
Output:
Vehicle is accelerating. Car is driving.
In this example, Car
inherits accelerate()
from Vehicle
.
2. Multiple Inheritance
Multiple inheritance lets a derived class inherit from multiple base classes. This combines features from several independent classes into one.
Example:
You have a Writer
class and an Artist
class. You create a WriterArtist
class that inherits from both.
#include <iostream>
// Base class 1
class Writer {
public:
void write() {
std::cout << "Writer is writing a story." << std::endl;
}
};
// Base class 2
class Artist {
public:
void draw() {
std::cout << "Artist is drawing a picture." << std::endl;
}
};
// Derived class inheriting from Writer and Artist
class WriterArtist : public Writer, public Artist {
public:
void create() {
std::cout << "WriterArtist is creating something new." << std::endl;
}
};
int main() {
WriterArtist person;
person.write(); // Inherited from Writer
person.draw(); // Inherited from Artist
person.create();
return 0;
}
Output:
Writer is writing a story. Artist is drawing a picture. WriterArtist is creating something new.
WriterArtist
has access to write()
from Writer
and draw()
from Artist
.
3. Multilevel Inheritance
Multilevel inheritance involves a chain of inheritance. A class inherits from a base class, and then another class inherits from this derived class.
Example:
You have a Animal
class. Then, Mammal
inherits from Animal
. Finally, Dog
inherits from Mammal
.
#include <iostream>
// Grandparent class
class Animal {
public:
void eat() {
std::cout << "Animal eats food." << std::endl;
}
};
// Parent class
class Mammal : public Animal {
public:
void breathe() {
std::cout << "Mammal breathes air." << std::endl;
}
};
// Child class
class Dog : public Mammal {
public:
void bark() {
std::cout << "Dog barks loudly." << std::endl;
}
};
int main() {
Dog myDog;
myDog.eat(); // Inherited from Animal
myDog.breathe(); // Inherited from Mammal
myDog.bark();
return 0;
}
Output:
Animal eats food. Mammal breathes air. Dog barks loudly.
Here, Dog
gets features from both Mammal
and Animal
.
4. Hierarchical Inheritance
Hierarchical inheritance occurs when multiple derived classes inherit from a single base class. This is useful for creating a family of related classes that share common characteristics.
Example:
You have a Shape
base class. Then, Circle
and Rectangle
both inherit from Shape
.
#include <iostream>
// Base class
class Shape {
public:
void display() {
std::cout << "This is a shape." << std::endl;
}
};
// Derived class 1
class Circle : public Shape {
public:
void drawCircle() {
std::cout << "Drawing a circle." << std::endl;
}
};
// Derived class 2
class Rectangle : public Shape {
public:
void drawRectangle() {
std::cout << "Drawing a rectangle." << std::endl;
}
};
int main() {
Circle myCircle;
myCircle.display(); // Inherited from Shape
myCircle.drawCircle();
Rectangle myRectangle;
myRectangle.display(); // Inherited from Shape
myRectangle.drawRectangle();
return 0;
}
Output:
This is a shape. Drawing a circle. This is a shape. Drawing a rectangle.
Both Circle
and Rectangle
reuse the display()
method from Shape
.
5. Hybrid (Virtual) Inheritance
Hybrid inheritance combines two or more types of inheritance. A common form is combining multiple and hierarchical inheritance. This can lead to the “Diamond Problem” where a class inherits common members from two parent classes that themselves share a common grandparent class. C++ solves this using virtual base classes.
The Diamond Problem:
Consider A
as a base class. B
and C
inherit from A
. Then, D
inherits from both B
and C
. D
gets two copies of A
‘s members.
#include <iostream>
class A {
public:
A() { std::cout << "A's constructor called." << std::endl; }
void func() { std::cout << "A's function." << std::endl; }
};
class B : public A {
public:
B() { std::cout << "B's constructor called." << std::endl; }
};
class C : public A {
public:
C() { std::cout << "C's constructor called." << std::endl; }
};
class D : public B, public C {
public:
D() { std::cout << "D's constructor called." << std::endl; }
};
int main() {
D obj;
// obj.func(); // Ambiguous call
return 0;
}
The call obj.func()
is ambiguous because D
has two func()
methods, one from B
and one from C
. Both originate from A
.
Solution: Virtual Base Classes
You use the virtual
keyword to make A
a virtual base class for B
and C
. This ensures D
gets only one copy of A
‘s members.
#include <iostream>
class A {
public:
A() { std::cout << "A's constructor called." << std::endl; }
void func() { std::cout << "A's function." << std::endl; }
};
class B : virtual public A { // Virtual inheritance
public:
B() { std::cout << "B's constructor called." << std::endl; }
};
class C : virtual public A { // Virtual inheritance
public:
C() { std::cout << "C's constructor called." << std::endl; }
};
class D : public B, public C {
public:
D() { std::cout << "D's constructor called." << std::endl; }
};
int main() {
D obj;
obj.func(); // Now works
return 0;
}
Output:
A's constructor called. B's constructor called. C's constructor called. D's constructor called. A's function.
Using virtual
ensures only one A
sub-object exists within D
.
Best Practices for Inheritance
Use these tips to make your inheritance code better:
- Favor Composition Over Inheritance: Use inheritance when a clear “is-a” relationship exists (e.g., Car is a Vehicle). Use composition when a “has-a” relationship exists (e.g., Car has an Engine). Composition often leads to more flexible designs.
- Keep Base Classes Simple: Design base classes with common, generic functionality. Avoid adding too many specific details.
- Use protected for Internal Access: Use protected members when derived classes need to access base class data, but external code should not.
- Define Clear Interfaces: Ensure base classes provide a clear interface for derived classes to implement or override. Use virtual functions for this.
- Avoid Deep Inheritance Hierarchies: Deep hierarchies become complex and difficult to manage. Keep your inheritance trees shallow.