Inheritance — public, protected & private Base
Inheritance creates an IS-A relationship: `class Dog : public Animal` says every Dog IS-AN Animal. The `public` specifier means Animal's public interface is accessible through Dog. `protected` inheritance makes it accessible only within Dog's hierarchy. `private` inheritance means Dog uses Animal's implementation but is not publicly an Animal. Real-world use: almost always `public` is correct for IS-A; `private` is for composition-via-inheritance (rare).
Inheritance Chains, Constructor Chaining & protected Access
#include <iostream>
#include <string>
#include <cmath>
// ── Base class ────────────────────────────────────────────────
class Animal {
std::string name_; // private — derived cannot access
protected:
int energy_; // protected — derived CAN access
public:
explicit Animal(std::string name, int energy = 100)
: name_(std::move(name)), energy_(energy)
{
std::cout << "Animal(" << name_ << ")
";
}
virtual ~Animal() { std::cout << "~Animal(" << name_ << ")
"; }
const std::string& name() const { return name_; }
int energy() const { return energy_; }
void eat(int calories) {
energy_ += calories;
std::cout << name_ << " eats, energy=" << energy_ << "
";
}
};
// ── Derived class ─────────────────────────────────────────────
class Dog : public Animal { // IS-A Animal
std::string breed_;
public:
Dog(std::string name, std::string breed)
: Animal(std::move(name), 80) // calls base constructor
, breed_(std::move(breed))
{
std::cout << "Dog(" << name() << ", " << breed_ << ")
";
}
~Dog() override { std::cout << "~Dog(" << name() << ")
"; }
void bark() const {
std::cout << name() << " [" << breed_ << "] barks! Energy: "
<< energy_ << "
"; // protected member accessible
}
void fetch() { energy_ -= 10; std::cout << name() << " fetches
"; }
const std::string& breed() const { return breed_; }
};
// ── Further derived ───────────────────────────────────────────
class GuideDog : public Dog {
std::string owner_name_;
public:
GuideDog(std::string name, std::string owner)
: Dog(std::move(name), "Labrador") // GuideDogs are Labradors
, owner_name_(std::move(owner))
{}
void guide() const {
std::cout << name() << " guides " << owner_name_ << "
";
}
};
int main() {
std::cout << "--- Construction order: base before derived ---
";
GuideDog gd{"Max", "Alice"};
gd.bark(); // Dog method
gd.guide(); // GuideDog method
gd.eat(50); // Animal method — inherited through chain
// Slicing — happens when passing by value to base type
Animal a = gd; // sliced: only Animal part copied — Dog/GuideDog parts gone!
std::cout << "Sliced name: " << a.name() << "
";
// Use references/pointers to avoid slicing
std::cout << "
--- Destruction order (reverse): ---
";
// gd destructs here: ~GuideDog, ~Dog, ~Animal
return 0;
}Common Mistakes
- Object slicing — passing a derived object by value to a base-type parameter copies only the base part. The derived-class members are lost. Always use `const Animal&` or `Animal*` for polymorphic parameters.
- Calling base constructor via `Animal()` instead of `Animal(std::move(name), ...)` — forgetting to pass constructor arguments to the base means the base is default-constructed (if it has a default constructor). Ensure the correct base constructor is called explicitly in the derived MIL.
- Not making the base destructor `virtual` — this is the most critical virtual function mistake. Without `virtual ~Animal()`, `delete animal_ptr` on a pointer to Dog won't call `~Dog()`. This is an immediate resource leak if Dog owns any resources.
Tip
Tip
Practice Inheritance public protected private Base in small, isolated examples before integrating into larger projects. Breaking concepts into small experiments builds genuine understanding faster than reading alone.
The four pillars of Object-Oriented Programming in C++
Practice Task
Note
Practice Task — (1) Write a working example of Inheritance public protected private Base from scratch without looking at notes. (2) Modify it to handle an edge case (empty input, null value, or error state). (3) Share your solution in the Priygop community for feedback.
Quick Quiz
Common Mistake
Warning
A common mistake with Inheritance public protected private Base is skipping edge case testing — empty inputs, null values, and unexpected data types. Always validate boundary conditions to write robust, production-ready cpp code.
Key Takeaways
- Inheritance creates an IS-A relationship: `class Dog : public Animal` says every Dog IS-AN Animal.
- Object slicing — passing a derived object by value to a base-type parameter copies only the base part. The derived-class members are lost. Always use `const Animal&` or `Animal*` for polymorphic parameters.
- Calling base constructor via `Animal()` instead of `Animal(std::move(name), ...)` — forgetting to pass constructor arguments to the base means the base is default-constructed (if it has a default constructor). Ensure the correct base constructor is called explicitly in the derived MIL.
- Not making the base destructor `virtual` — this is the most critical virtual function mistake. Without `virtual ~Animal()`, `delete animal_ptr` on a pointer to Dog won't call `~Dog()`. This is an immediate resource leak if Dog owns any resources.