Bcp121 Week 10 Lecture 1
Bcp121 Week 10 Lecture 1
Now, if I do:
Derived d;
d.print(); // Outputs: "Derived print"
That’s fine—it calls the Derived version. But watch this:
Base* ptr = new Derived();
ptr->print(); // Outputs: "Base print"
delete ptr; // Remember to delete allocated memory
What?! Why didn’t it call Derived::print()? This is static binding. The compiler looks at the
pointer type (Base*) and decides at compile time to call Base::print(), ignoring the actual object
type (Derived). This isn’t what we want for polymorphism. How do we fix it? Let’s find out.
Now:
Base* ptr = new Derived();
ptr->print(); // Outputs: "Derived print"
delete ptr;
Magic! This is dynamic binding—the decision about which print() to call happens at runtime,
based on the object’s actual type. How does this work? Behind the scenes, C++ uses a v-table
(virtual table)—a lookup table created for each class with virtual functions. When you call a
virtual function through a pointer, the program checks the v-table to find the right function. Don’t
worry about the details—just know virtual enables this runtime flexibility.
Let’s make it safer with a C++11 feature: the override specifier. Rewrite Derived like this:
class Derived : public Base {
public:
void print() override { cout << "Derived print\n"; }
};
override ensures you’re actually overriding a virtual function. If you typo the function name or
signature, the compiler will catch it. Pretty handy, right? Any questions about virtual functions or
dynamic binding?
If Base doesn’t have a virtual destructor, only Base::~Base() runs, and Derived::~Derived() is
skipped. This can cause memory leaks or undefined behavior if Derived allocates resources. Fix
it like this:
#include <iostream>
using namespace std;
class Base {
public:
virtual void print() { cout << "Base print\n"; }
// Make the destructor virtual!
virtual ~Base() { cout << "Base destroyed\n"; }
};
class Derived : public Base {
public:
void print() override { cout << "Derived print\n"; }
// Derived destructor is automatically virtual if base is
~Derived() override { cout << "Derived destroyed\n"; }
};
// Example usage:
// Base* ptr = new Derived();
// delete ptr; // Now calls Derived destructor then Base destructor
Now, delete ptr calls both destructors in the right order: Derived first, then Base. Rule of thumb:
If a class has any virtual functions, give it a virtual destructor. Got it? Let’s move to something
even more abstract.
Part 5: Abstract Base Classes & Pure Virtual Functions (30 minutes)
Sometimes, a base class isn’t meant to be instantiated—it’s just an interface. Enter Abstract
Base Classes (ABCs) and pure virtual functions. Here’s how:
#include <iostream>
using namespace std;
// Abstract Base Class (ABC)
class Shape {
public:
// Pure virtual function (makes Shape abstract)
virtual double area() const = 0;
// Virtual destructor is still needed for proper cleanup via base
pointer
virtual ~Shape() { cout << "Shape destroyed\n"; }
};
Derived classes must implement all pure virtual functions to become concrete:
#include <cmath> // For M_PI if available, otherwise use 3.14159
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) { cout << "Circle created\n"; }
~Circle() override { cout << "Circle destroyed\n"; }
// Provide implementation for the pure virtual function
double area() const override {
return 3.14159 * radius * radius; // Or use M_PI
}
};
ABCs define a contract—any derived class must provide an area() implementation (or remain
abstract itself). This is perfect for things like shapes, animals, or game objects where the base is
just a blueprint. Questions about ABCs or pure virtual functions?
Closing
That’s it for today’s lecture! You’re now equipped to handle polymorphism like pros. Bring your
energy to the lab—we’ll write code to see virtual functions and ABCs in action. See you there!