C++ Polymorphism

PolymorphismLiterally, it means multiple forms.

When there is a hierarchical structure among classes, and the classes are related through inheritance, polymorphism is used.

In C++, polymorphism is one of the important features of object-oriented programming.

C++ polymorphism allows using base class pointers or references to call overridden methods of derived classes, so that the same interface can exhibit different behaviors.

Polymorphism makes code more flexible and general. The program can operate on objects of different types through base class pointers or references without explicitly distinguishing object types. This makes code more extensible; for example, when adding a new shape class, the main program does not need to be modified.

Here are a few key points about polymorphism:

Virtual Functions:

  • Declaring a function in the base class as a virtual function uses the keywordvirtual。
  • A derived class can override this virtual function.
  • When a virtual function is called, which version of the function is called is determined by the actual type of the object.

Dynamic Binding:

  • Also known as late binding, the specific implementation of a function call is determined at runtime.
  • It requires using a pointer or reference to the base class to call virtual functions; at runtime, the compiler decides which function to call based on the actual type of the object.

Pure Virtual Functions:

  • A class containing pure virtual functions is called an abstract class, and it cannot be instantiated directly.
  • A pure virtual function has no function body; when declared, it uses the keyword= 0。
  • It forces derived classes to provide concrete implementations.

The implementation mechanism of polymorphism:

  • Virtual function table (V-Table): The C++ runtime uses a virtual function table to implement polymorphism. Every class containing virtual functions has a virtual function table, which stores pointers to all virtual functions in the class.
  • Virtual function pointer (V-Ptr): The object contains a pointer to the class's virtual function table.

The advantages of using polymorphism:

  • Code ReuseThrough base class pointers or references, different types of derived class objects can be manipulated, achieving code reuse.
  • ExtensibilityWhen adding a new derived class, code that depends on the base class does not need to be modified; you only need to ensure the new class correctly overrides the virtual functions.
  • DecouplingPolymorphism allows program design to be more modular and reduces coupling between classes.

Notes:

  • Polymorphism occurs only when a virtual function is called through a base class pointer or reference.
  • If a derived class object is used directly to call a function, then the version in the derived class is called, not the version in the base class.
  • Polymorphism requires runtime type information (RTTI), which may increase program overhead.

Example 1

Let's understand the application of polymorphism through a simple example:

Example 1

#include <iostream>
using namespace std;

// Base class Animal
class Animal {
public:
    // Virtual function sound, providing an interface for different animals to make sounds
    virtual void sound() const {
        cout << "Animal makes a sound" << endl;
    }
   
    // Virtual destructor ensures derived class objects are properly destructed
    virtual ~Animal() {
        cout << "Animal destroyed" << endl;
    }
};

// Derived class Dog, inheriting from Animal
class Dog : public Animal {
public:
    // Override the sound method
    void sound() const override {
        cout << "Dog barks" << endl;
    }
   
    ~Dog() {
        cout << "Dog destroyed" << endl;
    }
};

// Derived class Cat, inheriting from Animal
class Cat : public Animal {
public:
    // Override the sound method
    void sound() const override {
        cout << "Cat meows" << endl;
    }
   
    ~Cat() {
        cout << "Cat destroyed" << endl;
    }
};

// Test polymorphism
int main() {
    Animal* animalPtr;  // Base class pointer

    // Create Dog object and point to it with an Animal pointer
    animalPtr = new Dog();
    animalPtr->sound();  // Call Dog's sound method
    delete animalPtr;    // Free memory, call Dog and Animal destructors

    // Create Cat object and point to it with an Animal pointer
    animalPtr = new Cat();
    animalPtr->sound();  // Call Cat's sound method
    delete animalPtr;    // Free memory, call Cat and Animal destructors

    return 0;
}

The program execution output is:

Dog barks
Dog destroyed
Animal destroyed
Cat meows
Cat destroyed
Animal destroyed

Code Explanation

base classAnimal:

  • AnimalThe class defines a virtual functionsound(), which is a virtual function (virtual), used to represent the behavior of animals making sounds.
  • ~Animal()Being a virtual destructor ensures that when a derived class object pointed to by a base class pointer is released, the derived class destructor is correctly called, preventing memory leaks.

derived classDogandCat:

  • DogandCatClasses all inherit fromAnimalclasses derive from it, and each implementssound()method.
  • Dogofsound()Output "Dog barks";Catofsound()Outputs "Cat meows". This makes the same method (sound()) exhibit different behaviors in different classes.

main functionmain():

  • Create a base class pointeranimalPtr。
  • Usagenew Dog()CreateDogobject, assign its address toanimalPtr. At this point, the callanimalPtr->sound()It will output "Dog barks", becauseanimalPtractually points toDogObject.
  • releaseDogwhen the object, first callDogdestructor, then callAnimalthe destructor of.
  • Usagenew Cat()CreateCatobject and assign toanimalPtr, then callanimalPtr->sound(), outputs "Cat meows", showing polymorphic behavior.

Key concepts

  • virtual function: By using in the base classvirtualkeyword to declare virtual functions, derived classes can override this function, so that at runtime the correct function is called based on the object type.

  • dynamic binding: C++ polymorphism is implemented through dynamic binding. At runtime, the base class pointeranimalPtrwill, based on the actual type of the object it points to (DogorCat) call the correspondingsound()method.

  • virtual destructorIn a base class with polymorphic behavior, the destructor should be declared asvirtual, to ensure that when a derived class object is deleted, the derived class destructor is called, preventing resource leaks.


Example 2

In the example below, we implement a common Shape base class and two derived classes, Rectangle and Triangle, through polymorphism.

Calling different derived class methods through a base class pointer demonstrates the dynamic binding feature of polymorphism.

Example 2

#include <iostream> using namespace std; // Base class Shape, representing a shape class Shape { protected: int width, height; // Width and height public: // Constructor with default parameters Shape(int a = 0, int b = 0) : width(a), height(b) { } // Virtual function area, used to calculate area // Use the virtual keyword to implement polymorphism virtual int area() { cout << "Shape class area: " << endl; return 0; } }; // Derived class Rectangle, represents a rectangle class Rectangle : public Shape { public: // Constructor, uses base class constructor to initialize width and height Rectangle(int a = 0, int b = 0) : Shape(a, b) { } // Override the area function to calculate the rectangle area int area() override { cout << "Rectangle class area: " << endl; return width * height; } }; // Derived class Triangle, represents a triangle class Triangle : public Shape { public: // Constructor, uses base class constructor to initialize width and height Triangle(int a = 0, int b = 0) : Shape(a, b) { } // Override the area function to calculate the triangle area int area() override { cout << "Triangle class area: " << endl; return (width * height / 2); } }; // Main function int main() { Shape *shape; // Base class pointer Rectangle rec(10, 7); // Rectangle object Triangle tri(10, 5); // Triangle object // Point the base class pointer to the rectangle object, and call the area function shape = &rec; cout << "Rectangle Area: " << shape->area() << endl; // Point the base class pointer to the triangle object, and call the area function shape = &tri; cout << "Triangle Area: " << shape->area() << endl; return 0; }

When the above code is compiled and executed, it produces the following results:

Rectangle Area: Rectangle class area: 
70
Triangle Area: Triangle class area: 
25

Code analysis

Definition of the Shape class:

  • Shapeis an abstract base class, which defines a virtual functionarea()。area()is a virtual function used to calculate the area, and it uses thevirtualkeyword, so that in derived classes the function can be overridden, thereby achieving polymorphism.
  • widthandheightYesprotectedproperty, can only beShapeaccessed in the class and its derived classes.
// 基类 Shape,表示形状
class Shape {
   protected:
      int width, height; // 宽度和高度

   public:
      // 构造函数,带有默认参数
      Shape(int a = 0, int b = 0) : width(a), height(b) { }

      // 虚函数 area,用于计算面积
      virtual int area() {
         cout << "Shape class area: " << endl;
         return 0;
      }
};

Definition of the Rectangle class:

  • RectangleinheritsShapeclass, and overrodearea()method, calculates the area of the rectangle.
  • area()The method usesoverridekeyword, indicating that this is an override of the base classShapeofarea()Method overriding.
  • Rectangle::area()returnwidth * height, i.e., the area of the rectangle.
// 派生类 Rectangle,表示矩形
class Rectangle : public Shape {
   public:
      // 构造函数,使用基类构造函数初始化 width 和 height
      Rectangle(int a = 0, int b = 0) : Shape(a, b) { }

      // 重写 area 函数,计算矩形面积
      int area() override { 
         cout << "Rectangle class area: " << endl;
         return width * height;
      }
};

Definition of the Triangle class:

  • Trianglethe class also inherits fromShape, and overridesarea()method, used to calculate the area of the triangle.
  • Triangle::area()returnwidth * height / 2, this is the formula for the area of a triangle.
// 派生类 Triangle,表示三角形
class Triangle : public Shape {
   public:
      // 构造函数,使用基类构造函数初始化 width 和 height
      Triangle(int a = 0, int b = 0) : Shape(a, b) { }

      // 重写 area 函数,计算三角形面积
      int area() override { 
         cout << "Triangle class area: " << endl;
         return (width * height / 2); 
      }
};

Polymorphic behavior in the main function:

  • A base class pointer is definedshape, this pointer can point to anyShapeclass object or an object of its derived classes.
  • First, pointshapethe pointer toRectangleObjectrec, then callshape->area(). Sincearea()is a virtual function, and at this point it will dynamically bind toRectangle::area(), outputting the area of the rectangle.
  • Then, pointshapethe pointer toTriangleObjecttri, callshape->area()will be dynamically bound toTriangle::area(), outputting the area of the triangle.
// 主函数
int main() {
   Shape *shape;           // 基类指针
   Rectangle rec(10, 7);   // 矩形对象
   Triangle tri(10, 5);    // 三角形对象

   // 将基类指针指向矩形对象,并调用 area 函数
   shape = &rec;
   cout << "Rectangle Area: " << shape->area() << endl;

   // 将基类指针指向三角形对象,并调用 area 函数
   shape = &tri;
   cout << "Triangle Area: " << shape->area() << endl;

   return 0;
}

Key concepts

  • virtual function: in the base classShapevirtual functions are defined inarea(). The role of a virtual function is to allow derived classes to override this function and, at runtime, call the appropriate function implementation based on the actual object type the pointer points to.

  • dynamic binding: becausearea()is a virtual function,shape->area()when called, at runtime, based onshapeactual type of the object it points to (RectangleorTriangle) to call the correspondingarea()implementation. This mechanism of deciding which function to call at runtime is called dynamic binding and is the core of polymorphism.

  • polymorphism of base class pointers: the base class pointershapecan point to any object derived fromShapeof the object. Whenshapewhen pointing to different derived class objects, callingshape->area()will produce different behaviors, which reflects the characteristics of polymorphism.

  • virtual function

    virtual functionis to use the keyword in the base classvirtualthe declared function.

    virtual functionallows subclasses to override it, so that at runtime the overridden version of the subclass is called through a base class pointer or reference, implementing dynamic binding.

    What we want is that at any point in the program, the function to be called can be selected according to the type of the object being called. This operation is calleddynamic binding, orlate binding.

    Features:

    • can have an implementation in the base class. Usually, virtual functions provide a default implementation in the base class, but derived classes can choose to override them.
    • dynamic binding: at runtime, call the corresponding function version based on the actual type of the object.
    • Optional overrideDerived classes can optionally override virtual functions, but it is not mandatory.

    Example

    #include <iostream>
    using namespace std;

    class Animal {
    public:
        virtual void sound() {  // Virtual function
            cout << "Animal makes a sound" << endl;
        }
    };

    class Dog : public Animal {
    public:
        void sound() override {  // Override the virtual function
            cout << "Dog barks" << endl;
        }
    };

    int main() {
        Animal *animal = new Dog();
        animal->sound();  // Output: Dog barks
        delete animal;
    }

    In the above code, sound is a virtual function of the Animal class. When calling sound() through the Animal* pointer animal, the program selects Dog::sound() based on the actual object type (Dog).

    Pure virtual function

    You may want to define a virtual function in the base class so that you can redefine the function in a derived class to better suit the object, but you cannot provide a meaningful implementation for the virtual function in the base class. In this case, a pure virtual function is used.

    A pure virtual function is a virtual function without an implementation; in the base class it is declared with= 0to declare.

    A pure virtual function means the base class defines an interface, but the specific implementation is the responsibility of the derived class.

    Pure virtual functions make the base class an abstract class, which cannot be instantiated.

    Features:

    • must be declared in the base class as= 0, indicating there is no implementation, and derived classes must override it.
    • Abstract class: A class containing pure virtual functions cannot be instantiated directly; an object can only be created when all pure virtual functions are implemented by a derived class.
    • Interface definitionPure virtual functions are usually used to define interfaces, letting derived classes implement specific behaviors.

    We can rewrite the virtual function area() in the base class as follows:

    #include <iostream> using namespace std; class Shape { public: virtual int area() = 0; // Pure virtual function, forces subclasses to implement this method }; class Rectangle : public Shape { private: int width, height; public: Rectangle(int w, int h) : width(w), height(h) { } int area() override { // Implement the pure virtual function return width * height; } }; int main() { Shape *shape = new Rectangle(10, 5); cout << "Rectangle Area: " << shape->area() << endl; // Output: Rectangle Area: 50 delete shape; }

    = 0tells the compiler that the function has no body; the above virtual function isPure virtual function。

    Comparison of virtual functions and pure virtual functions

    FeaturesVirtual FunctionPure Virtual Function
    definitionUsed in the base classvirtualDeclaration, with implementationUsed in the base class= 0Declaration, without implementation
    Overridden in the derived classSubclasses may choose to overrideThe subclass must implement
    Abstractnesscan instantiate the classmakes the class an abstract class, which cannot be instantiated
    Purposeprovides default behavior, allowing subclasses to overridedefines an interface, forcing derived classes to implement specific behaviors
    other extensions