Inheritance in C++ with Base and Derived Class Examples
Introduction to Inheritance in C++
Inheritance is a fundamental concept in C++ and other object-oriented languages. It allows you to:
- Reuse code by sharing common data and behavior in a base class.
- Extend behavior by adding new members in derived classes.
- Model hierarchies such as
Shape → Circle,Vehicle → Car, orAccount → SavingsAccount.
Formally, a derived class is defined from a base class using a syntax like:
class Derived : public Base {
// additional members
};
Key terms:
- Base class: The class being inherited from.
- Derived class: The class that inherits from the base.
- Public inheritance: Models an “is-a” relationship and is the most common form.
- Protected/private inheritance: Used for more specialized design needs.
According to long-term industry surveys (for example, the JetBrains C++ ecosystem report), a large majority of C++ projects use object-oriented features such as classes and inheritance, especially in GUI, game, and large-scale application development. Mastering inheritance is therefore essential for real-world C++ work.
Example 1: Basic Inheritance with Animals
Context
This first example demonstrates a simple is-a relationship: a Dog is an Animal, and a Cat is an Animal. The base class provides common behavior, while derived classes add specific behavior.
Code: Simple Base and Derived Classes
#include <iostream>
#include <string>
// Base class
class Animal {
public:
void eat() const {
std::cout << "This animal is eating.\n";
}
void sleep() const {
std::cout << "This animal is sleeping.\n";
}
};
// Derived class 1
class Dog : public Animal {
public:
void bark() const {
std::cout << "Dog: Woof!\n";
}
};
// Derived class 2
class Cat : public Animal {
public:
void meow() const {
std::cout << "Cat: Meow!\n";
}
};
int main() {
Dog dog;
Cat cat;
dog.eat(); // Inherited from Animal
dog.bark(); // Specific to Dog
cat.sleep(); // Inherited from Animal
cat.meow(); // Specific to Cat
}
Explanation
Animalis the base class. It defines two common behaviors:eat()andsleep().DogandCatuse public inheritance (class Dog : public Animal) so thatDogandCatobjects can use the public interface ofAnimal.- Both derived classes automatically gain
eat()andsleep()in addition to their own functions (bark()andmeow()).
Important Note:
Use public inheritance when the derived class truly “is a” specialized form of the base class. For example,
Dogis anAnimal, but aCaris not aWheel.
Example 2: Inheritance with Constructors and protected Members
Context
Often, a base class contains data members that must be initialized. Constructors in derived classes can call base class constructors to ensure consistent initialization.
Code: Initializing Base Class State
#include <iostream>
#include <string>
class Animal {
protected: // accessible in derived classes
std::string name;
public:
explicit Animal(const std::string& animalName)
: name(animalName) {}
void eat() const {
std::cout << name << " is eating.\n";
}
};
class Dog : public Animal {
public:
explicit Dog(const std::string& dogName)
: Animal(dogName) {} // call base constructor
void bark() const {
std::cout << name << " says: Woof!\n";
}
};
int main() {
Dog dog("Buddy");
dog.eat(); // uses name from Animal
dog.bark(); // uses name from Animal
}
Explanation
- The base class
Animalhas aprotected std::string name;member, which is visible to derived classes but not to external code. - The
Animalconstructor initializesnameusing a member initializer list. - The
Dogconstructor explicitly calls the base constructor:Dog(const std::string& dogName) : Animal(dogName) {}. - This guarantees that
nameis always initialized beforeDog-specific code runs.
Pro Tip:
Prefer initializing base class and member variables using member initializer lists rather than assignment in the constructor body. This is more efficient and is required for
constand reference members.
Example 3: Multi-Level Inheritance (Animal → Dog → Puppy)
Context
Multi-level inheritance occurs when a class is derived from a derived class. This can model more specific categories: Animal → Dog → Puppy.
Code: Multi-Level Inheritance
#include <iostream>
#include <string>
class Animal {
protected:
std::string name;
public:
explicit Animal(const std::string& animalName)
: name(animalName) {}
void eat() const {
std::cout << name << " is eating.\n";
}
};
class Dog : public Animal {
public:
explicit Dog(const std::string& dogName)
: Animal(dogName) {}
void bark() const {
std::cout << name << " barks: Woof!\n";
}
};
class Puppy : public Dog {
public:
explicit Puppy(const std::string& puppyName)
: Dog(puppyName) {}
void play() const {
std::cout << name << " is playing with a ball.\n";
}
};
int main() {
Puppy p("Charlie");
p.eat(); // from Animal
p.bark(); // from Dog
p.play(); // from Puppy
}
Explanation
Puppyinherits fromDog, which in turn inherits fromAnimal.Puppyobjects have access to:eat()fromAnimalbark()fromDogplay()defined inPuppy
- Constructor calls chain from
Puppy→Dog→Animal.
Important Note:
Multi-level inheritance can be useful, but deep inheritance trees (4+ levels) can become hard to understand and maintain. Many style guides recommend keeping inheritance hierarchies relatively shallow.
Example 4: Virtual Functions and Runtime Polymorphism
Context
So far, each object is used through its concrete type (Dog, Cat, Puppy). In real projects, you often store pointers or references to the base class and call functions that behave differently depending on the actual derived type. This is runtime polymorphism.
Code: Virtual Function in Base, Overrides in Derived
#include <iostream>
#include <memory>
#include <vector>
class Animal {
public:
virtual ~Animal() = default; // virtual destructor
virtual void speak() const { // virtual function
std::cout << "Some generic animal sound.\n";
}
};
class Dog : public Animal {
public:
void speak() const override {
std::cout << "Dog: Woof!\n";
}
};
class Cat : public Animal {
public:
void speak() const override {
std::cout << "Cat: Meow!\n";
}
};
int main() {
std::vector<std::unique_ptr<Animal>> animals;
animals.push_back(std::make_unique<Dog>());
animals.push_back(std::make_unique<Cat>());
for (const auto& a : animals) {
a->speak(); // calls Dog::speak or Cat::speak at runtime
}
}
Explanation
virtual void speak() constinAnimalmarks the function as virtual, enabling dynamic dispatch.DogandCatuseoverrideto indicate they are overriding a virtual function from the base.- The
animalsvector storesstd::unique_ptr<Animal>; each element actually points to a different derived type. - At runtime, calling
a->speak()selects the correct implementation based on the actual object type, not the pointer type.
Pro Tip:
Always declare a virtual destructor in a polymorphic base class. This ensures derived destructors run correctly when deleting through a base pointer. This recommendation is also emphasized in many C++ best-practice resources, such as the C++ Core Guidelines.
Example 5: Access Specifiers in Inheritance (public, protected, private)
Context
C++ lets you control how base class members appear in the derived class through:
- Member access specifiers:
public,protected,private - Inheritance type:
public,protected,privateinheritance
This example focuses on public inheritance and protected data, which is common in practice.
Code: Using protected in a Base Class
#include <iostream>
#include <string>
class Employee {
protected:
std::string name;
double salary;
public:
Employee(const std::string& n, double s)
: name(n), salary(s) {}
void showBasicInfo() const {
std::cout << "Name: " << name
<< ", Salary: $" << salary << '\n';
}
};
class Manager : public Employee {
int teamSize;
public:
Manager(const std::string& n, double s, int team)
: Employee(n, s), teamSize(team) {}
void showDetails() const {
// name and salary are accessible here because they are protected
std::cout << "Manager: " << name
<< ", Salary: $" << salary
<< ", Team size: " << teamSize << '\n';
}
};
int main() {
Manager m("Alice", 95000.0, 8);
m.showBasicInfo();
m.showDetails();
}
Explanation
Employeeexposesnameandsalaryasprotected, so:- They are not accessible from outside the class.
- They are accessible inside
Manager, which inherits fromEmployee.
ManageraddsteamSizeand ashowDetails()function.
Important Note:
Use
protecteddata sparingly. Many experienced C++ developers prefer keeping dataprivateand exposing it throughprotectedgetters/setters instead. This keeps your base class more flexible if you need to change its implementation later.
Example 6: Multiple Inheritance (Interface-Style)
Context
C++ supports multiple inheritance, where a class can inherit from more than one base class. This can be powerful but must be used carefully to avoid ambiguity. A common, relatively safe pattern is inheriting from multiple interface-like classes (classes with only pure virtual functions).
Code: Logger + Serializable Interfaces
#include <iostream>
#include <string>
class Logger {
public:
virtual ~Logger() = default;
virtual void log(const std::string& message) const = 0; // pure virtual
};
class Serializable {
public:
virtual ~Serializable() = default;
virtual std::string toJson() const = 0; // pure virtual
};
class User : public Logger, public Serializable {
std::string username;
public:
explicit User(std::string name)
: username(std::move(name)) {}
void log(const std::string& message) const override {
std::cout << "[User log] " << username << ": " << message << '\n';
}
std::string toJson() const override {
return "{\"username\": \"" + username + "\"}";
}
};
int main() {
User u("jdoe");
u.log("Login successful");
std::cout << "JSON: " << u.toJson() << '\n';
}
Explanation
LoggerandSerializableact like interfaces: they have only pure virtual functions and virtual destructors.Userinherits from both and provides concrete implementations.- This pattern avoids many of the classic multiple-inheritance pitfalls (like the diamond problem) because there is no shared data and no ambiguous base state.
Pro Tip:
When using multiple inheritance, prefer inheriting from interface-style base classes (pure virtual, no data). This is similar to how interfaces are used in languages like Java and C# and is widely recommended in modern C++ practice.
Example 7: Abstract Base Classes and Pure Virtual Functions
Context
An abstract base class cannot be instantiated directly. It is used only as a base for other classes. In C++, a class is abstract if it has at least one pure virtual function.
This is useful for defining a common interface that all derived classes must implement.
Code: Shape Hierarchy with an Abstract Base Class
#include <iostream>
#include <cmath>
class Shape {
public:
virtual ~Shape() = default;
// pure virtual function: makes Shape an abstract class
virtual double area() const = 0;
};
class Circle : public Shape {
double radius;
public:
explicit Circle(double r) : radius(r) {}
double area() const override {
return M_PI * radius * radius; // requires <cmath>
}
};
class Rectangle : public Shape {
double width;
double height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const override {
return width * height;
}
};
int main() {
Circle c(3.0);
Rectangle r(4.0, 5.0);
Shape* shapes[] = { &c, &r };
for (Shape* s : shapes) {
std::cout << "Area: " << s->area() << '\n';
}
}
Explanation
Shapehas a pure virtual functionarea() = 0;, so it cannot be instantiated.CircleandRectanglemust implementarea().- Client code can work with
Shape*pointers without knowing the concrete type.
This pattern is widely used in GUI frameworks, game engines, and scientific computing libraries to define common operations on different concrete types. For example, many numerical libraries define abstract interfaces for linear algebra objects and let specific implementations (CPU vs. GPU) override the behavior.
Important Note:
Abstract base classes help you separate interface (what operations are available) from implementation (how those operations are done). This separation is a key design principle in large C++ systems and is discussed extensively in university courses and references such as MIT OpenCourseWare.
Professional Tips for Using Inheritance in C++
- Prefer composition over inheritance when the relationship is “has-a” rather than “is-a”.
- Keep base classes small and focused to reduce coupling and make changes safer.
- Avoid fragile base class problems by not exposing too many implementation details to derived classes.
- Use
overrideconsistently for all virtual function overrides to catch errors at compile time. - Document the intended usage of each base class (is it an interface, a mixin, or a concrete base?). Clear documentation reduces misuse in large teams.
For deeper design guidance, see the ISO C++ Foundation’s C++ Core Guidelines, which include detailed sections on class design and inheritance.
Frequently Asked Questions (FAQ)
1. When should I use inheritance instead of composition in C++?
Use inheritance when there is a clear “is-a” relationship and you need polymorphism (e.g., Shape → Circle). Use composition when one object logically “has” another (e.g., Car has an Engine). Many modern C++ style guides suggest starting with composition and introducing inheritance only when it provides a clear benefit.
2. What is the difference between public, protected, and private inheritance?
- Public inheritance: Public and protected members of the base keep their access level in the derived class interface (public stays public, protected stays protected). This models “is-a” and is used most often.
- Protected inheritance: Public and protected members of the base become protected in the derived class. This is rare and usually used for implementation details.
- Private inheritance: Public and protected members of the base become private in the derived class. This models a “implemented-in-terms-of” relationship and is often better expressed with composition.
3. Why do I need a virtual destructor in a base class?
If you delete a derived object through a pointer to base (e.g., Animal* a = new Dog; delete a;), and the base class does not have a virtual destructor, the derived destructor may not run. This can cause resource leaks and undefined behavior. Declaring a virtual destructor in a polymorphic base class ensures the correct destructor chain executes.
4. Is multiple inheritance safe to use in C++?
Multiple inheritance is part of the language and can be used safely, especially when:
- You inherit from interface-like classes (pure virtual, no data), and
- You avoid complex diamonds of inheritance with shared data.
However, it increases complexity and can introduce ambiguity, so many teams restrict its use to well-defined patterns such as interfaces and mixins.
5. Are virtual functions slow in performance-critical code?
Virtual function calls are slightly more expensive than non-virtual calls because they use an indirection through a vtable. For most applications, this overhead is negligible compared to I/O, memory, and algorithmic costs. In extremely performance-critical inner loops (e.g., high-frequency trading, real-time physics), developers may avoid virtual dispatch and use templates or other techniques instead. Profiling is essential before optimizing away virtual calls.
Summary
Inheritance in C++ lets you create base and derived classes that share behavior, specialize functionality, and support runtime polymorphism. By understanding constructors, access specifiers, multi-level inheritance, virtual functions, abstract classes, and multiple inheritance patterns, you can design class hierarchies that are both powerful and maintainable.
The examples in this guide give you a practical foundation. From here, you can explore more advanced topics such as templates with inheritance, mixin classes, and policy-based design, all of which build on the same core concepts you have seen here.
Related Topics
Practical examples of polymorphism in C++: function overloading
Best examples of dynamic memory allocation in C++: new and delete
Practical examples of C# variable declaration and initialization examples
Modern examples of C++ operator overloading: custom operators examples that actually matter
Best examples of C# conditional statements: examples & explanations
Real‑world examples of C# exception handling: 3 practical patterns every developer should know
Explore More C++ Code Snippets
Discover more examples and insights in this category.
View All C++ Code Snippets