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.cpp37.3PolymorphismWithVirtualFunctions/main.cpp37.4SizeOfPolymorphicObjectsAndSlicing/main.cpp37.5PolymorphicObjectsStoredInCollections/main.cpp37.6Override/main.cpp37.7OverloadingOverridingAndHiding/main.cpp37.8PolymorphismAtDifferentLevels/main.cpp37.9InheritanceAndPolymorphismWithStaticMembers/main.cpp37.10Final/main.cpp37.11FinalAndOverrideAreNotKeywords/main.cpp37.12PolymorphicFunctionsAndAccessSpecifiers/main.cpp37.13NonPolymorphicFunctionsAndAccessSpecifiers/main.cpp37.14VirtualFunctionsWithDefaultArguments/main.cpp37.15VirtualDestructors/main.cpp37.16DynamicCasts/main.cpp37.17PolymorphicFunctionsAndDestructors/main.cpp37.18TypeIdOperator/main.cpp37.19PureVirtualFunctionsAndAbstractClasses/main.cpp37.20AbstractClassesAsInterfaces/main.cpp