Virtual functions provide a powerful mechanism for creating reusable code in C++ through the concept of polymorphism. Here's how it works:
- Base Class and Derived Classes:
- You define a base class that contains virtual functions. These virtual functions outline the general behavior that derived classes can customize.
- You create derived classes that inherit from the base class. These derived classes provide their own specific implementations of the virtual functions.
- Polymorphism:
- You can create pointers (or references) of the base class type.
- These pointers can point to objects of both the base class "and" any of its derived classes.
- When you call a virtual function through a base class pointer, the actual function called is determined at runtime based on the type of object the pointer refers to. This is known as runtime polymorphism or dynamic dispatch.
How This Enables Code Reuse
Let's imagine an example:
class Shape {
public:
virtual double calculateArea() = 0; // Pure virtual function
};
class Circle : public Shape {
public:
double radius;
double calculateArea() override {
return 3.14159 * radius * radius;
}
};
class Rectangle : public Shape {
public:
double width, height;
double calculateArea() override {
return width * height;
}
};
Now, consider this code:
std::vector<Shape*> shapes;
shapes.push_back(new Circle{radius: 5});
shapes.push_back(new Rectangle{width: 4, height: 6});
for (Shape* shape : shapes) {
double area = shape->calculateArea();
std::cout << "Area: " << area << std::endl;
}
The beauty here is:
- Reusable Interface: The `Shape` class defines a common interface (`calculateArea`) that all shapes must implement.
- Flexibility: You can easily add new shape types (Triangle, Square, etc.) by deriving them from `Shape` and overriding `calculateArea`.
- Extensibility: The loop that calculates areas doesn't need to be modified *at all* to handle new shape types.
Key Takeaways
- Virtual functions allow you to write code that works with a general interface (the base class), while the specific behavior is determined by the concrete type of object (derived classes) at runtime.
- This makes your code more reusable, extensible, and easier to maintain.
The keyword virtual is a function specifier that causes function call selection at runtime, and it can only be used to modify member function declarations. The combination of virtual functions and public inheritance will be our most general and flexible way to build a piece of software. This is a form of pure polymorphism. Virtual functions allow runtime decisions. Consider a computer-aided design application in which the area of the shapes in a design has to be computed. The different shapes will be derived from the shape base class:
class shape {
public:
virtual double area() const { return 0; }
//virtual double area is default behavior
protected:
double x, y;
};
class rectangle : public shape {
public:
double area() const { return (height * width); }
private:
double height, width;
};
class circle : public shape {
public:
double area() const
{ return (PI * radius * radius);}
private:
double radius;
};
In such a class hierarchy, the derived classes correspond to important, well understood types of shapes. The system is readily expanded by deriving further classes. The area calculation is a local responsibility of a derived class. Client code that uses the polymorphic area calculation looks like this:
shape* p[N];
.....
for (i = 0; i < N; ++i)
tot_area += p[i] -> area();
A major advantage here is that the client code will not need to change if new shapes are added to the system.
Change is managed locally and propagated automatically by the polymorphic character of the client code.