Prerequisites for Dynamic Polymorphism
Dynamic polymorphism in C++ requires two specific conditions to be met:
- The invocation must occur through a pointer or reference of the base class type.
- The called method must be a virtual function that has been overridden in the derived class.
Virtual Functions and Overriding
A virtual function is a member function declared using the virtual keyword. It signals to the compiler that this function should be resolved at runtime rather than compile-time.
class BaseEntity {
public:
virtual void ProcessAction() { std::cout << "Standard execution" << std::endl; }
};Overriding (also known as covering) occurs when a derived class defines a virtual function with an identical signature—matching return type, method name, and parameter list—to one in the base class.
class DerivedEntity : public BaseEntity {
public:
virtual void ProcessAction() { std::cout << "Specialized execution" << std::endl; }
// Omitting 'virtual' here still constitutes an override due to inherited attributes,
// but explicitly specifying it is strongly recommended for clarity.
};Exceptions to Strict Signature Matching
There are two notable exceptions where the overriding signature does not match the base exactly:
- Covariant Return Types: A derived virtual method may return a pointer or reference to a derived type, while the base method returns a pointer or reference to the base type.
- Virtual Destructors: While destructors in base and derived classes have different names, they still form an override relationship if the base destructor is virtual. The compiler internally normalizes destructor names to a uniform identifier (e.g.,
destructor), enabling proper cleanup through base pointers. Declaring the base destructor as virtual is critical to prevent resource leaks when deleting derived objects via base pointers.
C++11 Override Control: override and final
To prevent subtle errors caused by signature mismatches that silently break polymorphism, C++11 introduced two keywords:
override: Appended to a derived class method to explicitly declare the intent to override. The compiler will emit an error if the method does not perfectly match a base virtual function.final: Applied to a method to prevent further overriding in subsequent derived classes, or applied to a class to prohibit any inheritance from it.
Abstract Classes and Pure Virtual Functions
Appending = 0 to a virtual function declaration designates it as a pure virtual function. A class containing at least one pure virtual function becomes an abstract class (or interface class) and cannot be instantiated. Derived classes inheriting from an abstract class must override all pure virtual functions; otherwise, they remain abstract and also cannot be instantiated. This mechanism enforces a strict contract for interface implementation.
Interface Inheritance vs Implementation Inheritance
Standard member function inheritance transfers the function's implementation to the derived class. Conversely, virtual function inheritance primarily transfers the interface—the function signature. The derived class inherits the obligation to provide its own implementation to fulfill the polymorphic contract. Therefore, functions should only be declared virtual if polymorphic behavior is genuinely required.
Underlying Mechanics: The Virtual Table
The core mechanism enabling dynamic polymorphism is the virtual function table (vtable). Each class with virtual functions maintains a vtable, which is essentially an array of function pointers pointing to the most derived version of each virtual method. Each object instance contains a hidden pointer (vptr) referencing its class's vtable. When a virtual method is called via a base pointer, the runtime dereferences the vptr, consults the vtable, and dispatches the call to the correct function implementation.
Static Binding vs Dynamic Binding
- Static Binding (Early Binding): The function call is resolved during compilation. Function overloading is an example of static polymorphism.
- Dynamic Binding (Late Binding): The function call is resolved at runtime based on the actual object type pointed to by the base reference or pointer. This relies on the vtable mechanism.
Regarding memory layout, virtual function implementations reside in the code segment, the vtables typically reside in the read-only data segment, and the vptr is stored directly within the memory footprint of each object instance.