C++ Notes
class Base { virtual void method(); }; class Derived : public Base { void method() override; }; Base* obj = new Derived(); obj->method(); // calls Derived::method() at runtime
| Static Binding (compile time) | Dynamic Binding (runtime) |
|---|---|
| Method call resolved at compile time. The compiler uses the declared pointer or reference type to decide which method runs, it doesn't care about the actual object at runtime. This is the default behavior in C++ for all non-virtual functions, and it's faster since there's no runtime overhead. | Method call resolved at runtime. The compiler uses the actual object type to decide which method runs. Requires the virtual keyword on the base method, and works through base pointers and references managing derived objects. Slightly slower due to the vtable lookup. |
Shape s; Shape* ptr = &s; Shape& ref = s; ptr->display(); // virtual: resolved at runtime based on actual object ptr->info(); // non-virtual: resolved at compile time based on pointer type
virtual is written on the base class method. It enables dynamic binding for that function.override is written on the derived class method. It tells the compiler to verify that this function is actually overriding a virtual base function.override isn't compulsory, but strongly recommended. Without it, a typo in the function name or signature silently creates a new, unrelated function instead of an override, and no error is thrown.virtual. Derived classes automatically inherit the virtual nature, but writing virtual again in the derived class is allowed and harmless.class Shape { public: virtual void display(); // virtual on base }; class Circle : public Shape { public: void display() override; // override on derived };
| Overloading | Overriding | Hiding |
|---|---|---|
| Multiple versions of the same function name in one class, with different parameter types or counts. Resolved at compile time. Same class only. | A new implementation of a base virtual function in a derived class, with the same name and same signature. Resolved at runtime via the vtable. Requires virtual in the base. |
The derived class defines a function with the same name as the base, but the base function is not virtual. The base version is hidden, not overridden, no polymorphism happens, a base pointer still calls the base version. |
Overriding one overload of a virtual function hides all other overloads from the base in the child class. If you need the other overloads too, you must explicitly override each one, or bring them back with using Base::functionName; inside the derived class. Overloads created only in the child class also don't participate in polymorphism at all, a base pointer can't call them even if the object is actually of the derived type.
sizeof(Circle) > sizeof(Shape).Circle circle(5); Shape shape = circle; // slicing: Circle-specific data is lost shape.display(); // calls Shape::display(), NOT Circle::display()
Always use pointers or references when working with polymorphism. Storing by value causes slicing and silently breaks dynamic dispatch. Arrays of base class objects cause slicing too, the moment you push derived objects into them.
Circle c1(5), c2(10), c3(15); // Wrong: slicing happens, derived part is lost // Shape shapes[]{c1, c2, c3}; // Wrong: arrays cannot hold references // const Shape& shapes[]{c1, c2, c3}; // Correct: array of base pointers Shape* shapes[] = { &c1, &c2, &c3 }; for (int i = 0; i < 3; i++) { shapes[i]->display(); // calls Circle::display() for each }
| final on a class | final on a method |
|---|---|
No other class can inherit from this class, it's the last in the inheritance chain. You can still override virtual functions inside the final class itself. Syntax: class Shape final { ... }; |
No further derived class can override this specific method, but the class itself is still inheritable. Useful when you want to lock down one specific behavior while allowing other extensions. Syntax: virtual void display() final; |
class Shape { public: virtual void display(int x = 10); // base default }; class Circle : public Shape { public: void display(int x = 20) override; // derived default (ignored via base pointer) }; Shape* ptr = new Circle(); ptr->display(); // calls Circle::display BUT uses x=10 (base default!)
To avoid confusion, don't define different default arguments in base and derived virtual functions. Either keep them the same, or avoid default arguments on virtual functions altogether and use explicit calls instead.
virtual on the base destructor, only the base destructor runs, the derived destructor is completely skipped.virtual enables dynamic dispatch for destructors too: the most derived destructor runs first, then each parent destructor up the chain.class Shape { public: virtual ~Shape() { /* cleanup */ } // virtual destructor }; Shape* s = new Circle(7); delete s; // with virtual: Circle destructor runs first, then Shape destructor // without virtual: ONLY Shape destructor runs (Circle resources leak)
dynamic_cast performs a safe runtime check. If the actual object isn't of the target type, it returns nullptr for pointer casts.nullptr, which makes it harder to check safely.Shape* shape = new Circle(5); Circle* circle = dynamic_cast<Circle*>(shape); if (circle != nullptr) { circle->circleOnlyMethod(); // safe to use } else { std::cout << "Cast failed: object is not a Circle\n"; }
This is a well-known C++ trap. The code compiles cleanly and looks correct, but the virtual call resolves statically to the base version. Never call virtual functions from constructors or destructors.
= 0.class Shape { public: virtual double area() const = 0; // pure virtual: no body here virtual ~Shape() = default; }; // Shape s; ERROR: cannot instantiate abstract class class Circle : public Shape { double radius; public: Circle(double r) : radius(r) {} double area() const override { return 3.14159 * radius * radius; } }; Shape* s = new Circle(5); // base pointer to derived: works fine
class Drawable { public: virtual void draw() = 0; // pure virtual: must be implemented virtual ~Drawable() = default; }; class Square : public Drawable { public: void draw() override { std::cout << "Drawing Square\n"; } }; Drawable* d = new Square(); d->draw(); // prints: Drawing Square
#include <iostream> // ============================================================ // BASE CLASS: virtual display (dynamic), info (static) // ============================================================ class Shape { public: virtual void display() { // virtual: dynamic binding std::cout << "Shape display\n"; } void info() { // non-virtual: static binding std::cout << "Shape info\n"; } virtual ~Shape() { // virtual destructor: REQUIRED for polymorphism std::cout << "Shape destroyed\n"; } }; // ============================================================ // DERIVED CLASS // ============================================================ class Circle : public Shape { private: int radius; public: Circle(int r = 0) : radius(r) {} void display() override { // overrides Shape::display std::cout << "Circle display, radius: " << radius << '\n'; } void info() { // hides Shape::info (NOT overriding) std::cout << "Circle info\n"; } ~Circle() { std::cout << "Circle destroyed\n"; } }; // ============================================================ // STATIC MEMBERS // ============================================================ class Counter { public: static int count; Counter() { count++; } static void showCount() { std::cout << "Count: " << count << '\n'; } }; int Counter::count = 0; // ============================================================ // VIRTUAL WITH DEFAULT ARGUMENT (subtle gotcha) // ============================================================ class Base { public: virtual void greet(int x = 10) { // base default: 10 std::cout << "Base greet: " << x << '\n'; } }; class Derived : public Base { public: void greet(int x = 20) override { // derived default: 20 (ignored via base ptr) std::cout << "Derived greet: " << x << '\n'; } }; // ============================================================ // PURE VIRTUAL (abstract base) and INTERFACE // ============================================================ class Drawable { // pure interface: no data, no body public: virtual void draw() = 0; virtual ~Drawable() = default; }; class Square : public Drawable { public: void draw() override { std::cout << "Drawing Square\n"; } }; // ============================================================ // MAIN // ============================================================ int main() { std::cout << "\n--- Static vs Dynamic Binding ---\n"; Shape s; Shape* sp = &s; Shape& sr = s; s.display(); // dynamic: Shape::display sp->display(); // dynamic: Shape::display sr.display(); // dynamic: Shape::display s.info(); // static: Shape::info (pointer type decides) sp->info(); // static: Shape::info std::cout << "\n--- Polymorphic Collection ---\n"; Circle c1(5), c2(10), c3(15); Shape* shapes[] = { &c1, &c2, &c3 }; // base pointers to derived objects for (int i = 0; i < 3; i++) { shapes[i]->display(); // calls Circle::display for each } std::cout << "\n--- Object Slicing ---\n"; Shape sliced = c1; // Circle-specific members stripped sliced.display(); // calls Shape::display (dynamic dispatch lost) std::cout << "\n--- Name Hiding (info is non-virtual) ---\n"; c1.info(); // Circle::info (direct call on Circle object) shapes[0]->info(); // Shape::info (base pointer, non-virtual = static binding) std::cout << "\n--- Static Members ---\n"; Counter a, b; Counter::showCount(); // prints: Count: 2 std::cout << "\n--- Virtual Default Argument Gotcha ---\n"; Base* bptr = new Derived(); bptr->greet(); // calls Derived::greet but uses x=10 (base default!) delete bptr; std::cout << "\n--- Pure Virtual / Interface ---\n"; Square sq; Drawable* dptr = &sq; dptr->draw(); // prints: Drawing Square std::cout << "\n--- Virtual Destructor ---\n"; Shape* s2 = new Circle(7); delete s2; // virtual destructor: Circle destructor runs first, then Shape destructor // without virtual: only Shape destructor would run (Circle resources leak) std::cout << "\n--- Object Sizes (vtable overhead) ---\n"; std::cout << "sizeof(Shape): " << sizeof(Shape) << "\n"; std::cout << "sizeof(Circle): " << sizeof(Circle) << "\n"; // Circle is larger: includes Shape data + int radius + vptr return 0; }
That's the full C++ series, ground up from hello world to vtables and abstract interfaces. Polymorphism is really where everything from the earlier posts, pointers, references, inheritance, object lifetime, comes together into the one mechanism that makes a single line of code, shapes[i]->display();, correctly call a different function for every object in the array.