Theory
One loop must serve every card
Closing time. The till loops over today's swipes to compute discounts:
for each Card *c in swipes: total += c->discount();
The list mixes StudentCards and StaffCards, and next year GuestCards join. The loop must stay one line, yet every card must apply its own rule.
You saw the promise of run-time polymorphism two lessons ago. Today you meet the two keywords that actually deliver it: virtual, and its stricter sibling = 0.
Theory
The blank menu board
Head office hangs a menu board template in every franchise: the slot "Today's discount" is printed on it, but head office leaves the value blank, each branch must fill in its own.
A virtual function is a board entry branches may rewrite. A pure virtual function is the blank slot: head office refuses to open a branch that has not filled it in. The template itself is not a shop you can walk into.
Theory
Virtual function, formally
A virtual function is a member function declared with virtual in the base class:
virtual double discount() { return 0.0; }
Its power appears with base-class pointers: Card *c = new StudentCard; followed by c->discount() runs StudentCard's version, because virtual switches the call to late binding (decided at run time by the actual object).
Without virtual, the pointer's own type wins and the base version runs: that single keyword is the whole difference.
Theory
Pure virtual, formally
Write = 0 instead of a body and the function becomes pure virtual:
virtual double discount() = 0;
Two consequences, both exam gold:
- The class becomes an abstract class: creating objects of it is a compile error.
- Every derived class must override the pure virtual function, or it stays abstract itself.
An abstract class is a contract: it says "every card WILL have a discount rule" without pretending to know what the rule is.
Practical
The contract and its signers
#include <iostream>
using namespace std;
class Card { // abstract class
public:
virtual double discount() = 0; // pure virtual: the blank slot
virtual ~Card() {} // virtual destructor: good practice
};
class StudentCard : public Card {
public:
double discount() { return 0.10; } // must implement
};
class StaffCard : public Card {
public:
double discount() { return 0.20; } // must implement
};
int main() {
// Card c; // ERROR: cannot create abstract class object
Card *swipes[2] = { new StudentCard, new StaffCard };
for (int i = 0; i < 2; i++)
cout << swipes[i]->discount() << endl; // 0.1 then 0.2
delete swipes[0]; delete swipes[1];
return 0;
}
Think first
Predict both outputs
Take the code above and imagine discount() WITHOUT the virtual keyword (and without = 0, give it a body returning 0.0). What does the loop print then? And with the code as written?
Show the answer
Without virtual: the pointers are Card, so early binding calls Card's version twice: it prints 0 and 0*. The children's rules exist but are never reached through the base pointer.
As written (virtual): late binding asks each actual object, printing 0.1 then 0.2.
Same loop, one keyword apart. If an exam shows this pair and asks for outputs, you now have both.
Quiz
class Card has virtual double discount() = 0;. A student writes Card c; in main(). What does the compiler say?
- Error: Card is an abstract class, objects of it cannot be created
- Fine: c.discount() will simply return 0
- Error: classes cannot contain the digit 0
- Fine, but only if StudentCard exists somewhere
Show the answer
Error: Card is an abstract class, objects of it cannot be created
One pure virtual function is enough to make Card abstract, and abstract classes cannot be instantiated: there is literally no code behind discount() to run. You can still declare pointers and references to Card (that is the whole point). Option B confuses = 0 with "returns 0": it means "no body exists", not "body returns zero".
Watch out
Three precision points
1. = 0 means no implementation, not "returns zero".
2. A derived class that skips overriding a pure virtual function stays abstract: the contract follows until someone signs.
3. Abstract classes CAN have data members, normal functions, even constructors; abstract only forbids direct objects. Writing "abstract classes contain only pure virtual functions" loses a mark: that stricter thing is called an interface style, not a requirement.
Theory
The pattern you will reuse forever
Abstract base + concrete children is the skeleton of real software: Shape with area() = 0, Employee with calcSalary() = 0, every plugin system you will ever meet. When the vtable is mentioned in interviews, it is simply the lookup table C++ builds to make this run-time routing work. Concept over mechanism: base pointer in hand, actual object decides.
Summary
Key takeaways
- virtual makes a call through a base pointer/reference run the derived override (late binding).
- Without virtual, the pointer's type decides and the base version runs.
- Pure virtual (= 0) has no body; one such function makes the class abstract.
- Abstract classes cannot be instantiated, but pointers/references to them are the workhorse.
- Children must override every pure virtual function or remain abstract.
- Abstract class = contract: every card WILL define discount().
- Memory hook: the blank menu board every branch must fill.