Inheritance in C++
"Every scribe who has been trained for the kingdom of heaven is like a master of a house, who brings out of his treasure what is new and what is old." — Matthew 13:52
Why This Matters
You've built classes that work in isolation. Now we build classes that share structure.
Inheritance lets one class build on another. A Car is a Vehicle, so you write Vehicle once with the shared parts, and
Car inherits and adds only what's car-specific.
Polymorphism is the payoff: if you have a Vehicle* that's
actually pointing at a Car, and you call vehicle->describe(), the
right describe runs — Car's, not Vehicle's. The runtime dispatches based on what the
object actually is. You can put a mixed array in Vehicle*[] and iterate, and each
reports itself correctly.
The Syntax
class Argument {
protected:
string label, claim;
public:
Argument(string l, string c) : label(l), claim(c) {}
virtual ~Argument() {} // ← virtual destructor
virtual void defend() const { // ← virtual method
cout << "[" << label << "] " << claim << endl;
}
};
class Cosmological : public Argument {
public:
Cosmological() : Argument("Cosmological", "...") {}
void defend() const override { // ← override
// Cosmological-specific case
}
}; Key vocabulary:
: public Argument— Cosmological inherits from Argument.protected— visible to subclasses, invisible outside.virtual— this method can be overridden; dispatch is dynamic.override— affirms the derived method overrides a virtual. Compiler will reject typos.
Always Make Destructors Virtual in Polymorphic Hierarchies
Argument* a = new Cosmological();
delete a;
If ~Argument is not virtual, the compiler only calls ~Argument on
delete — not ~Cosmological. The Cosmological-specific cleanup is skipped.
Virtual destructor or memory leaks.
Pure Virtual = Abstract
class Argument {
public:
virtual void defend() const = 0; // pure virtual — no body
};
A class with any pure virtual is abstract. You cannot instantiate it
directly. Every concrete subclass must implement defend or it too becomes
abstract. This forces the contract.
Slicing — A Common Bug
void inspect(Argument a) { // ⚠ pass by value
a.defend();
}
Cosmological c;
inspect(c); // SLICED — the Cosmological parts are gone
Pass by value copies a base-sized chunk; the derived parts are sliced off. To
keep polymorphism alive: pass by pointer or reference (Argument* or
const Argument&), never by value.
Coach's Note — No single argument is "the" argument for God. The classical case is several arguments working together — each strong against different objections, each shoring up the others. That's exactly what polymorphism over a case file is doing in code. The widget above is the case file in motion.
This Week's Project
You're ready for Project 12: Argument Case File. Build an abstract
Argument base with three concrete subclasses (Cosmological, Moral, Ontological).
A CaseFile stores them as Argument* and iterates polymorphically.
Real attributions required — every defend() must cite a real source you can
verify.
Inheritance & Polymorphism — Quick Check
Argument* a = new Cosmological(); and call a->defend(). Which version runs?defend is marked virtual in Argument, dispatch happens at runtime based on the dynamic type. a actually points to a Cosmological, so Cosmological::defend() fires. Without the virtual keyword, the base version would run — the classic missing-virtual bug. class Argument {
public:
~Argument() {} // not virtual
};
Argument* a = new Cosmological();
delete a; delete a only invokes ~Argument(). Cosmological's destructor never fires; if Cosmological holds any resources (like a vector of premises) they leak. Rule: any class with virtual methods must have a virtual destructor. void inspect(Argument a) { // by value
a.defend();
}
Cosmological c;
inspect(c); c; the Cosmological-specific fields are sliced off. Inside inspect, a is a pure Argument. Fix: change the parameter to const Argument& or Argument*. CaseFile holding multiple Argument* entries, each defending characteristically. A case file with only one Argument* would defeat the pedagogical point.