Implementation Mechanisms of C++ Polymorphism
Polymorphism is one of the core characteristics of object-oriented programming. C++ implements compile-time polymorphism and runtime polymorphism through virtual functions and inheritance mechanisms.
Types of Polymorphism
1. Compile-time Polymorphism (Static Polymorphism)
- Function overloading
- Operator overloading
- Templates (generic programming)
2. Runtime Polymorphism (Dynamic Polymorphism)
- Virtual functions
- Pure virtual functions
- Abstract classes
Virtual Functions Implementing Polymorphism
Basic example:
cppclass Animal { public: virtual void makeSound() { std::cout << "Animal makes a sound" << std::endl; } virtual ~Animal() {} }; class Dog : public Animal { public: void makeSound() override { std::cout << "Dog barks" << std::endl; } }; class Cat : public Animal { public: void makeSound() override { std::cout << "Cat meows" << std::endl; } }; // Using polymorphism void animalSound(Animal* animal) { animal->makeSound(); // Dynamic binding } int main() { Dog dog; Cat cat; animalSound(&dog); // Output: Dog barks animalSound(&cat); // Output: Cat meows return 0; }
Pure Virtual Functions and Abstract Classes
Pure virtual functions:
cppclass Shape { public: virtual double area() = 0; // Pure virtual function virtual double perimeter() = 0; virtual ~Shape() {} // Virtual destructor }; class Rectangle : public Shape { private: double width; double height; public: Rectangle(double w, double h) : width(w), height(h) {} double area() override { return width * height; } double perimeter() override { return 2 * (width + height); } }; class Circle : public Shape { private: double radius; public: Circle(double r) : radius(r) {} double area() override { return 3.14159 * radius * radius; } double perimeter() override { return 2 * 3.14159 * radius; } }; // Usage void printShapeInfo(Shape* shape) { std::cout << "Area: " << shape->area() << std::endl; std::cout << "Perimeter: " << shape->perimeter() << std::endl; } int main() { Rectangle rect(5, 3); Circle circle(2); printShapeInfo(&rect); // Rectangle implementation printShapeInfo(&circle); // Circle implementation return 0; }
Virtual Destructors
Why virtual destructors are needed:
cppclass Base { public: virtual ~Base() { std::cout << "Base destructor" << std::endl; } }; class Derived : public Base { private: int* data; public: Derived() : data(new int[100]) {} ~Derived() { delete[] data; std::cout << "Derived destructor" << std::endl; } }; // Correct usage Base* ptr = new Derived(); delete ptr; // Correctly calls Derived and Base destructors // If Base destructor is not virtual class BaseNoVirtual { public: ~BaseNoVirtual() { // Non-virtual destructor std::cout << "BaseNoVirtual destructor" << std::endl; } }; class DerivedNoVirtual : public BaseNoVirtual { private: int* data; public: DerivedNoVirtual() : data(new int[100]) {} ~DerivedNoVirtual() { delete[] data; std::cout << "DerivedNoVirtual destructor" << std::endl; } }; // Wrong usage BaseNoVirtual* ptr2 = new DerivedNoVirtual(); delete ptr2; // Only calls BaseNoVirtual destructor, memory leak!
override Keyword
Using override to ensure correct overriding:
cppclass Base { public: virtual void func1() {} virtual void func2(int x) {} }; class Derived : public Base { public: void func1() override { // Correct override std::cout << "Derived func1" << std::endl; } // void func2() override; // Compilation error: parameter mismatch // void func3() override; // Compilation error: Base has no func3 };
final Keyword
Prevent further overriding or inheritance:
cppclass Base { public: virtual void func() final { // Cannot be overridden std::cout << "Base func" << std::endl; } }; class Derived1 : public Base { public: // void func() override; // Compilation error: func is final }; class FinalClass final { // Cannot be inherited public: void method() {} }; // class Derived2 : public FinalClass {}; // Compilation error: FinalClass is final
Runtime Type Identification (RTTI)
dynamic_cast:
cppclass Animal { public: virtual ~Animal() {} }; class Dog : public Animal { public: void bark() { std::cout << "Woof!" << std::endl; } }; class Cat : public Animal { public: void meow() { std::cout << "Meow!" << std::endl; } }; void processAnimal(Animal* animal) { if (Dog* dog = dynamic_cast<Dog*>(animal)) { dog->bark(); } else if (Cat* cat = dynamic_cast<Cat*>(animal)) { cat->meow(); } } // Usage Dog dog; Cat cat; processAnimal(&dog); // Output: Woof! processAnimal(&cat); // Output: Meow!
typeid:
cpp#include <typeinfo> class Base { public: virtual ~Base() {} }; class Derived : public Base {}; int main() { Base* base = new Derived(); std::cout << "Type name: " << typeid(*base).name() << std::endl; std::cout << "Is Derived: " << (typeid(*base) == typeid(Derived)) << std::endl; delete base; return 0; }
Practical Applications of Polymorphism
Strategy Pattern:
cppclass SortStrategy { public: virtual void sort(std::vector<int>& data) = 0; virtual ~SortStrategy() {} }; class BubbleSort : public SortStrategy { public: void sort(std::vector<int>& data) override { std::cout << "Using Bubble Sort" << std::endl; // Bubble sort implementation } }; class QuickSort : public SortStrategy { public: void sort(std::vector<int>& data) override { std::cout << "Using Quick Sort" << std::endl; // Quick sort implementation } }; class Sorter { private: SortStrategy* strategy; public: void setStrategy(SortStrategy* s) { strategy = s; } void sort(std::vector<int>& data) { strategy->sort(data); } }; // Usage Sorter sorter; BubbleSort bubbleSort; QuickSort quickSort; std::vector<int> data = {5, 2, 8, 1, 9}; sorter.setStrategy(&bubbleSort); sorter.sort(data); // Using bubble sort sorter.setStrategy(&quickSort); sorter.sort(data); // Using quick sort
Observer Pattern:
cppclass Observer { public: virtual void update(const std::string& message) = 0; virtual ~Observer() {} }; class Subject { private: std::vector<Observer*> observers; public: void attach(Observer* observer) { observers.push_back(observer); } void detach(Observer* observer) { observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end()); } void notify(const std::string& message) { for (auto observer : observers) { observer->update(message); } } }; class EmailObserver : public Observer { public: void update(const std::string& message) override { std::cout << "Email: " << message << std::endl; } }; class SMSObserver : public Observer { public: void update(const std::string& message) override { std::cout << "SMS: " << message << std::endl; } }; // Usage Subject subject; EmailObserver emailObserver; SMSObserver smsObserver; subject.attach(&emailObserver); subject.attach(&smsObserver); subject.notify("Hello World!"); // Output: // Email: Hello World! // SMS: Hello World!
Compile-time Polymorphism
Function overloading:
cppclass Printer { public: void print(int value) { std::cout << "Int: " << value << std::endl; } void print(double value) { std::cout << "Double: " << value << std::endl; } void print(const std::string& value) { std::cout << "String: " << value << std::endl; } }; // Usage Printer printer; printer.print(42); // Calls print(int) printer.print(3.14); // Calls print(double) printer.print("Hello"); // Calls print(string)
Operator overloading:
cppclass Complex { private: double real; double imag; public: Complex(double r = 0, double i = 0) : real(r), imag(i) {} Complex operator+(const Complex& other) const { return Complex(real + other.real, imag + other.imag); } Complex operator*(const Complex& other) const { return Complex(real * other.real - imag * other.imag, real * other.imag + imag * other.real); } friend std::ostream& operator<<(std::ostream& os, const Complex& c) { os << c.real << " + " << c.imag << "i"; return os; } }; // Usage Complex c1(1, 2); Complex c2(3, 4); Complex sum = c1 + c2; Complex product = c1 * c2; std::cout << sum << std::endl; // 4 + 6i std::cout << product << std::endl; // -5 + 10i
Templates (Static Polymorphism):
cpptemplate <typename T> T add(T a, T b) { return a + b; } // Usage int intResult = add(10, 20); // T = int double doubleResult = add(3.14, 2.71); // T = double // CRTP (Curiously Recurring Template Pattern) template <typename Derived> class Base { public: void interface() { static_cast<Derived*>(this)->implementation(); } }; class Derived : public Base<Derived> { public: void implementation() { std::cout << "Derived implementation" << std::endl; } }; // Usage Derived d; d.interface(); // Output: Derived implementation
Best Practices
1. Always declare virtual destructors for polymorphic base classes
cppclass Base { public: virtual ~Base() = default; // Virtual destructor };
2. Use override keyword to ensure correct overriding
cppclass Derived : public Base { public: void func() override { // Explicitly indicates override // Implementation } };
3. Prefer composition over inheritance
cpp// Recommended: composition class Engine { public: void start() { std::cout << "Engine started" << std::endl; } }; class Car { private: Engine engine; public: void start() { engine.start(); } }; // Not recommended: excessive inheritance class Vehicle { public: virtual void start() = 0; }; class Car : public Vehicle { public: void start() override { std::cout << "Car started" << std::endl; } };
4. Avoid overusing dynamic_cast
cpp// Not recommended if (Dog* dog = dynamic_cast<Dog*>(animal)) { dog->bark(); } else if (Cat* cat = dynamic_cast<Cat*>(animal)) { cat->meow(); } // Recommended: use virtual functions class Animal { public: virtual void makeSound() = 0; }; class Dog : public Animal { public: void makeSound() override { bark(); } void bark() { std::cout << "Woof!" << std::endl; } };
Notes
- Virtual function calls have performance overhead, avoid overuse in performance-critical paths
- Calling virtual functions in constructors and destructors does not result in dynamic binding
- Don't call pure virtual functions in constructors
- Virtual functions cannot be static member functions
- Avoid calling virtual functions in base class constructors
- Consider using the final keyword to prevent unnecessary overriding
- Use RTTI cautiously, as it may affect performance and maintainability