Notes

Thoughts, write-ups, and things worth remembering.

Virtual functions and polymorphism

#cpp#oop Updated Sep 28, 2026

Same function name, different behavior — depending on the actual type of the object, not the type of the pointer you’re holding it through.

Static vs dynamic binding

class Base    { void greet() { std::cout << "Base"; } };
class Derived : public Base { void greet() { std::cout << "Derived"; } };

Base* b = new Derived();
b->greet();   // prints "Base"

No virtual → resolved at compile time, based on the pointer’s declared type. No vtable lookup happens. The compiler just picks Base::greet and moves on with its life.

class Base    { virtual void greet() { std::cout << "Base"; } };
class Derived : public Base { void greet() override { std::cout << "Derived"; } };

Base* b = new Derived();
b->greet();   // prints "Derived"

virtual → resolved at runtime, based on what the object actually is. This is the whole point of polymorphism.

Pure virtual & abstract classes

class Node {
    virtual glm::mat3 computeTransform() const = 0;
};

= 0 means “no default implementation, and you can’t instantiate Node directly.” Every concrete subclass must supply one, or it stays abstract too.

Virtual destructors

class Node {
    virtual ~Node() = default;   // do this if you have ANY virtual function
};

Delete a Derived* through a Base* with no virtual destructor, and only ~Base() runs. ~Derived() gets silently skipped — a resource leak with zero error messages to point you at it.

override — a safety net, not decoration

void computeTransform() override;         // matches base? great.
void computeTransform() const override;   // forgot the const above? boom.

Mismatch the signature (missing const is the classic one) and skip override, and the compiler assumes you meant to write a brand-new, unrelated function. No error — until your class fails to implement the pure virtual and becomes abstract itself, surfacing as a confusing “cannot instantiate” error somewhere else entirely. override turns that into an error on the exact line you actually got wrong.

Slicing

Derived d;
Base b = d;   // slice! b is just a Base now

Assigning a derived object into a base-by-value variable chops off everything the base doesn’t know about. Derived’s extra data quietly disappears. Pass and store by pointer or reference to dodge this entirely. Only happens in pass by copy.

dynamic_cast

Base* b = getSomeNode();
if (Derived* d = dynamic_cast<Derived*>(b)) {
    // safe: b really was pointing at a Derived
}

Checks the actual runtime type before letting you treat something as a more specific subclass. Fails → returns nullptr instead of garbage. Always check it. static_cast skips this check and just trusts you — which is how you end up reading memory that was never yours to read.

Rule of Five and deep copy