Skip to content

Ch19: Polymorphism

TL;DR: Dynamic (Late) Binding: Virtual functions (virtual) prompt the compiler to defer method resolution to runtime, ensuring executions dispatch according to the object's dynamic type.

โšก Quick Reference

#include <iostream>
#include <memory>
#include <typeinfo>

class Base {
public:
    virtual void draw() const { std::cout << "Base draw" << std::endl; }
    virtual ~Base() = default;
};

class Derived : public Base {
public:
    void draw() const override { std::cout << "Derived draw" << std::endl; }
    ~Derived() override final = default;
};

class AbstractInterface {
public:
    virtual void execute() = 0;
    virtual ~AbstractInterface() = default;
};

int main() {
    Derived d;
    Base* ptr = &d;
    ptr->draw();

    Derived* p_derived = dynamic_cast<Derived*>(ptr);
    std::cout << typeid(*ptr).name() << std::endl;

    return 0;
}

๐Ÿง  Core Concepts

  • Dynamic (Late) Binding: Virtual functions (virtual) prompt the compiler to defer method resolution to runtime, ensuring executions dispatch according to the object's dynamic type.
  • Object Slicing: Copying a derived class object into a base class value slices away all derived members and virtual bindings; polymorphism only functions through references or pointers.
  • Abstract Interface Hierarchies: Pure virtual functions (= 0) declare abstract interfaces that cannot be instantiated directly, forcing subclasses to implement specific interfaces to satisfy compilation rules.

โš ๏ธ Pitfalls (Quick Scan)

Mistake Fix
Declaring a non-virtual destructor in a polymorphic base class Always declare base class destructors as virtual in any inheritance hierarchy
Assigning a derived object to a base-class value type (object slicing) Use pointers (Shape*) or references (Shape&) to maintain polymorphic behavior
Calling dynamic_cast on a non-polymorphic class hierarchy Ensure the base class has a virtual function (such as a virtual destructor)
Calling typeid on raw pointers directly Dereference the pointer first: typeid(*ptr)
Using default parameters with virtual function overrides Avoid using default arguments on virtual function declarations
Assuming final and override are reserved keywords in all contexts Treat them as contextual specifiers, avoiding their use as normal variable names
Declaring static members inside base and derived classes Avoid accessing static class variables through polymorphic pointers
Attempting to instantiate an abstract class Only use abstract classes as bases for polymorphic pointers and references
Declaring virtual destructors as private in base classes Ensure virtual destructors are public in base classes

๐Ÿ“– Full Details

Cause โ†’ Effect โ†’ Fix with timestamp (click to expand) * **Declaring a non-virtual destructor in a polymorphic base class** -> **Deleting a derived object through a base pointer causes undefined behavior and leaks memory (derived destructor is skipped)** -> **Always declare base class destructors as `virtual` in any inheritance hierarchy (37:15)** * **Assigning a derived object to a base-class value type (object slicing)** -> **Discards all derived-specific member variables and virtual method bindings, rendering the object non-polymorphic** -> **Use pointers (`Shape*`) or references (`Shape&`) to maintain polymorphic behavior (37:4)** * **Calling `dynamic_cast` on a non-polymorphic class hierarchy** -> **Compile-time error because dynamic casting requires at least one virtual function in the base** -> **Ensure the base class has a virtual function (such as a virtual destructor) (37:16)** * **Calling `typeid` on raw pointers directly** -> **Returns type details of the pointer type rather than the pointed-to dynamic object** -> **Dereference the pointer first: `typeid(*ptr)` (37:18)** * **Using default parameters with virtual function overrides** -> **The default arguments used are resolved by the static pointer type, not the dynamic object type** -> **Avoid using default arguments on virtual function declarations (37:14)** * **Assuming `final` and `override` are reserved keywords in all contexts** -> **Using them in unrelated contexts compiles, but leads to confusing code layouts** -> **Treat them as contextual specifiers, avoiding their use as normal variable names (37:11)** * **Declaring static members inside base and derived classes** -> **Static members are shadowed rather than shared polymorphically, resolving static types only** -> **Avoid accessing static class variables through polymorphic pointers (37:9)** * **Attempting to instantiate an abstract class** -> **Compile-time error because classes with pure virtual functions are incomplete** -> **Only use abstract classes as bases for polymorphic pointers and references (37:19)** * **Declaring virtual destructors as private in base classes** -> **Outside code cannot delete base pointers, causing resource cleanup blocks** -> **Ensure virtual destructors are public in base classes (37:19)**

๐Ÿ“Ž Repo Files

  • 37.2StaticBindingWithInheritance/main.cpp
  • 37.3PolymorphismWithVirtualFunctions/main.cpp
  • 37.4SizeOfPolymorphicObjectsAndSlicing/main.cpp
  • 37.5PolymorphicObjectsStoredInCollections/main.cpp
  • 37.6Override/main.cpp
  • 37.7OverloadingOverridingAndHiding/main.cpp
  • 37.8PolymorphismAtDifferentLevels/main.cpp
  • 37.9InheritanceAndPolymorphismWithStaticMembers/main.cpp
  • 37.10Final/main.cpp
  • 37.11FinalAndOverrideAreNotKeywords/main.cpp
  • 37.12PolymorphicFunctionsAndAccessSpecifiers/main.cpp
  • 37.13NonPolymorphicFunctionsAndAccessSpecifiers/main.cpp
  • 37.14VirtualFunctionsWithDefaultArguments/main.cpp
  • 37.15VirtualDestructors/main.cpp
  • 37.16DynamicCasts/main.cpp
  • 37.17PolymorphicFunctionsAndDestructors/main.cpp
  • 37.18TypeIdOperator/main.cpp
  • 37.19PureVirtualFunctionsAndAbstractClasses/main.cpp
  • 37.20AbstractClassesAsInterfaces/main.cpp