7.0 What This Teaches
- Inheritance - is-a: derived class extends a base class
- Virtual functions - runtime polymorphism through base-class pointers
- Composition - has-a (owned): member lifetime tied to owner
- Aggregation - has-a (non-owned): member outlives or predates owner
- Using - uses-a: function or class depends on another type temporarily
7.1 Inheritance
class Animal {
public:
std::string name;
explicit Animal(std::string n) : name(std::move(n)) {}
virtual ~Animal() = default; // virtual destructor required for polymorphism
};
class Dog : public Animal { // Dog is-an Animal; public makes base interface visible
public:
explicit Dog(std::string n) : Animal(std::move(n)) {}
};
class, inheritance defaults to private; always write
: public Base to preserve the base class's public interface.
// access control
class Base {
public: int pub = 1; // accessible everywhere
protected: int prot = 2; // accessible inside Base and derived classes
private: int priv = 3; // accessible only inside Base
};
7.2 Virtual Functions and Polymorphism
class Shape {
public:
virtual double area() const { return 0.0; }
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
double radius;
explicit Circle(double r) : radius(r) {}
double area() const override { return 3.14159 * radius * radius; }
};
class Rect : public Shape {
public:
double w, h;
Rect(double w, double h) : w(w), h(h) {}
double area() const override { return w * h; }
};
// polymorphism: base-class pointer dispatches to derived override at runtime
void print_area(const Shape& s) {
std::cout << s.area() << "\n"; // calls Circle::area or Rect::area
}
Circle c(3.0);
Rect r(4.0, 5.0);
print_area(c); // 28.274...
print_area(r); // 20
virtual when the class is
used polymorphically. Without it, deleting a derived object through a
base pointer only calls the base destructor, leaking derived resources.
Use override on every derived override - it lets the
compiler catch typos and signature mismatches.
7.3 Abstract Base Classes
class Drawable {
public:
virtual void draw() const = 0; // pure virtual: must be overridden
virtual ~Drawable() = default;
};
// Drawable cannot be instantiated directly:
// Drawable d; // error: abstract class
class Square : public Drawable {
public:
void draw() const override { std::cout << "drawing square\n"; }
};
Square sq;
sq.draw(); // ok
7.4 Composition
class Engine {
public:
int horsepower;
explicit Engine(int hp) : horsepower(hp) {}
};
class Car {
public:
Engine engine; // composed member: owned, lifetime tied to Car
std::string model;
Car(std::string m, int hp) : model(std::move(m)), engine(hp) {}
};
Car car("Sedan", 200);
std::cout << car.engine.horsepower << "\n"; // 200
// engine destroyed when car goes out of scope
unique_ptr when polymorphism is needed.
7.5 Aggregation
class Manager {
public:
std::string name;
explicit Manager(std::string n) : name(std::move(n)) {}
};
class Department {
public:
const Manager* head; // non-owning: Manager exists independently
std::string dept;
Department(std::string d, const Manager* m) : dept(std::move(d)), head(m) {}
};
Manager alice("Alice"); // lifetime independent of Department
Department eng("Engineering", &alice);
std::cout << eng.head->name << "\n"; // Alice
7.6 Using Relationships
// uses-a: function depends on Logger but does not store or own it
void process(const std::vector<int>& data, Logger& log) {
log.write("processing " + std::to_string(data.size()) + " items");
// ... use data ...
}
// uses-a: local variable used temporarily within a scope
void run() {
Timer t; // used locally; no lasting relationship
do_work();
std::cout << t.elapsed() << "\n";
}
7.7 Example - All Together
// Relationships - Inheritance, Composition, Aggregation, Using.
#include <iostream>
#include <string>
#include <vector>
#include <memory>
class Shape {
public:
std::string color;
explicit Shape(std::string c) : color(std::move(c)) {}
virtual double area() const { return 0.0; }
virtual void describe() const {
std::cout << color << " shape, area=" << area() << "\n";
}
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
double radius;
Circle(std::string c, double r) : Shape(std::move(c)), radius(r) {}
double area() const override { return 3.14159 * radius * radius; }
};
class Rect : public Shape {
public:
double w, h;
Rect(std::string c, double w, double h) : Shape(std::move(c)), w(w), h(h) {}
double area() const override { return w * h; }
};
class Engine { public: int hp; explicit Engine(int h) : hp(h) {} };
class Car {
public:
Engine engine;
std::string model;
Car(std::string m, int hp) : model(std::move(m)), engine(hp) {}
};
class Manager { public: std::string name; explicit Manager(std::string n) : name(std::move(n)) {} };
class Department {
public:
const Manager* head;
std::string dept;
Department(std::string d, const Manager* m) : dept(std::move(d)), head(m) {}
};
void print_area(const Shape& s) { std::cout << s.color << " area=" << s.area() << "\n"; }
int main() {
std::cout << "--- inheritance ---\n";
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>("red", 3.0));
shapes.push_back(std::make_unique<Rect>("blue", 4.0, 5.0));
for (const auto& s : shapes) s->describe();
std::cout << "\n--- composition ---\n";
Car car("Sedan", 200);
std::cout << car.model << " " << car.engine.hp << "hp\n";
std::cout << "\n--- aggregation ---\n";
Manager mgr("Alice");
Department dept("Engineering", &mgr);
std::cout << dept.dept << " head: " << dept.head->name << "\n";
std::cout << "\n--- using ---\n";
Circle c("green", 2.0);
print_area(c);
return 0;
}
--- inheritance ---
red shape, area=28.2743
blue shape, area=20
--- composition ---
Sedan 200hp
--- aggregation ---
Engineering head: Alice
--- using ---
green area=12.5664
7.8 Exercise
Exercise
- Define an abstract base class
Vehiclewith a pure virtualfuel_type(). DeriveElectricCarandGasTruckfrom it and store them in avector<unique_ptr<Vehicle>>. - Add a
Batteryclass and compose it intoElectricCarby value. Confirm its destructor runs when the car is destroyed. - Create a
Fleetclass that holds a non-owningconst Vehicle*to a lead vehicle (aggregation). Show that the Fleet can be destroyed while the vehicle lives on.
7.9 Common Mistakes
Missing virtual destructor
class Base { public: virtual void f() {} }; // no virtual ~Base
class Derived : public Base { public: std::string data; };
Base* p = new Derived();
delete p; // undefined behavior: ~Derived never called, data leaked
virtual ~Base() = default;.Object slicing
Circle c("red", 3.0);
Shape s = c; // sliced: only the Shape part is copied
s.area(); // calls Shape::area(), not Circle::area()
Forgetting override
class Derived : public Base {
public:
void f() const {} // silently defines a new function if Base::f() is not const
// add override to get a compile error instead
};
override. The compiler will then reject
any signature mismatch rather than silently creating a new function.7.10 Key Terms
| Term | Meaning |
|---|---|
| inheritance (is-a) | Derived class extends base; inherits members and can override virtual functions |
| virtual | Marks a function for runtime dispatch through base-class pointer or reference |
| override | Confirms a function overrides a base virtual; compile error if signature mismatches |
| pure virtual (= 0) | Must be overridden; makes the class abstract (non-instantiable) |
| virtual destructor | Required on any polymorphic base to ensure correct cleanup of derived objects |
| composition (has-a owned) | Member stored by value or unique_ptr; lifetime tied to owner |
| aggregation (has-a non-owned) | Member stored as raw pointer/reference; lifetime independent of owner |
| using (uses-a) | Transient dependency via parameter or local variable; no stored member |
| object slicing | Copying a derived to a base value loses the derived portion; use pointer or reference |