Inheritance: The Is-A Relationship
Inheritance models taxonomic relationships where a derived class “is a” specialized version of a base class. C++ supports single and multiple inheritance with three access modes.
class Shape {
protected:
std::string color_;
public:
Shape(std::string c) : color_(c) {}
virtual double area() const = 0; // Pure virtual
virtual ~Shape() = default;
};
class Circle : public Shape {
double radius_;
public:
Circle(std::string c, double r) : Shape(c), radius_(r) {}
double area() const override {
return 3.14159 * radius_ * radius_;
}
};
Inheritance Access Modes
| Derivation | Base public → Derived | Base protected → Derived | Base private → Derived |
|---|---|---|---|
| public | public | protected | inaccessible |
| protected | protected | protected | inaccessible |
| private | private | private | inaccessible |
Public inheritance models “is-a”: Circle is-a Shape. Most common. Protected inheritance models “implemented-in-terms-of” (rare). Private inheritance models “implemented-in-terms-of” (prefer composition).
class Stack : private std::vector<int> { // Private inheritance
public:
void push(int x) { push_back(x); }
int pop() { int x = back(); pop_back(); return x; }
};
// Stack is NOT a std::vector publicly
Virtual Functions and Dynamic Dispatch
Virtual functions enable runtime polymorphism: the called function depends on the dynamic type of the object, not the static type of the pointer/reference.
class Animal {
public:
virtual void speak() const {
std::cout << "Some sound\\n";
}
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void speak() const override { // override keyword: C++11
std::cout << "Woof!\\n";
}
};
void makeNoise(const Animal& a) {
a.speak(); // Dynamic dispatch
}
Dog d;
makeNoise(d); // Outputs: Woof!
The Virtual Table Mechanism
Virtual functions are implemented via a virtual table (vtable): a compile-time array of function pointers for each polymorphic class. Each object contains a hidden vptr pointing to its class’s vtable.
Cost: One pointer per object, one indirection per virtual call. This is the “overhead” of polymorphism.
Abstract Classes and Pure Virtual Functions
A pure virtual function has no implementation and is declared with = 0. Classes with pure virtuals are abstract: cannot be instantiated.
class Drawable {
public:
virtual void draw() const = 0; // Pure virtual
virtual ~Drawable() = default;
};
class Rectangle : public Drawable {
public:
void draw() const override {
// Implementation
}
};
// Drawable d; // Error: cannot instantiate abstract class
Rectangle r; // OK: Rectangle is concrete
Interface idiom: Abstract class with only pure virtuals and no data members.
The Override and Final Specifiers
C++11 added override and final to catch errors:
class Base {
public:
virtual void foo(int x);
virtual void bar() const;
};
class Derived : public Base {
public:
void foo(double x) override; // Error: doesn't override (different signature)
void bar() override; // Error: doesn't override (missing const)
};
final prevents further overriding or derivation:
class Base {
public:
virtual void method() final; // Cannot be overridden
};
class Derived final : public Base { // Cannot be further derived
};
Virtual Destructors: A Critical Rule
If a class has virtual functions, its destructor must be virtual. Otherwise, deleting a derived object through a base pointer causes undefined behavior:
class Base {
public:
~Base() { std::cout << "Base destroyed\\n"; }
};
class Derived : public Base {
int* data_;
public:
Derived() : data_(new int[100]) {}
~Derived() { delete[] data_; } // CRITICAL: must run
};
Base* p = new Derived;
delete p; // UB: Only Base destructor runs, memory leak!
Solution: Make base destructor virtual:
class Base {
public:
virtual ~Base() { } // Virtual destructor
};
Slicing Problem
Assigning a derived object to a base object slices off the derived parts:
class Base { int x_; };
class Derived : public Base { int y_; };
Derived d;
Base b = d; // Slicing: y_ is lost
Base& ref = d; // OK: Reference preserves dynamic type
Base* ptr = &d; // OK: Pointer preserves dynamic type
Rule: Pass polymorphic types by pointer or reference, never by value.
What happens if a base class destructor is NOT virtual?
Interactive Lab
Waiting for signal...