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 keyword
virtual。 - 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
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 pointer
animalPtr。 - Usage
new Dog()CreateDogobject, assign its address toanimalPtr. At this point, the callanimalPtr->sound()It will output "Dog barks", becauseanimalPtractually points toDogObject. - release
Dogwhen the object, first callDogdestructor, then callAnimalthe destructor of. - Usage
new Cat()CreateCatobject and assign toanimalPtr, then callanimalPtr->sound(), outputs "Cat meows", showing polymorphic behavior.
Key concepts
virtual function: By using in the base class
virtualkeyword 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 pointer
animalPtrwill, 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 as
virtual, 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
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 defined
shape, this pointer can point to anyShapeclass object or an object of its derived classes. - First, point
shapethe 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, point
shapethe 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 class
Shapevirtual 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: because
area()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 pointer
shapecan 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.- 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.
- 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.
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:
Example
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:
We can rewrite the virtual function area() in the base class as follows:
= 0tells the compiler that the function has no body; the above virtual function isPure virtual function。
Comparison of virtual functions and pure virtual functions
| Features | Virtual Function | Pure Virtual Function |
|---|---|---|
| definition | Used in the base classvirtualDeclaration, with implementation | Used in the base class= 0Declaration, without implementation |
| Overridden in the derived class | Subclasses may choose to override | The subclass must implement |
| Abstractness | can instantiate the class | makes the class an abstract class, which cannot be instantiated |
| Purpose | provides default behavior, allowing subclasses to override | defines an interface, forcing derived classes to implement specific behaviors |