Pure virtual functions are a special type of virtual function that have no implementation in the base class and must be implemented by any derived class that wants to be instantiated. They are a key feature in C++ for creating abstract classes and implementing interfaces.
What is a Pure Virtual Function?
A pure virtual function is a virtual function that has no implementation in the base class and is set to 0. The syntax is:
virtual return_type function_name(parameters) = 0;
For example:
class Shape {
public:
virtual double area() = 0; // Pure virtual function
};
Abstract Classes
A class that contains at least one pure virtual function is called an abstract class. Key characteristics of abstract classes include:
- Cannot be instantiated directly: You cannot create objects of an abstract class.
- Used as base classes: They serve as interfaces for derived classes.
- Must be implemented: Any derived class must implement all the pure virtual functions to be instantiable.
- Can have normal methods: Abstract classes can still have data members and normal (non-pure virtual) methods.
#include <iostream>
using namespace std;
// Abstract class
class Shape {
public:
// Pure virtual functions
virtual double area() = 0;
virtual double perimeter() = 0;
// Normal method
void printInfo() {
cout << "Area: " << area() << endl;
cout << "Perimeter: " << perimeter() << endl;
}
};
// Concrete class
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;
}
};
int main() {
// Shape shape; // Error: Cannot instantiate an abstract class
Circle circle(5);
circle.printInfo();
Shape* shapePtr = &circle; // Valid: Can point to derived class
shapePtr->printInfo();
return 0;
}
Output:
Area: 78.5397
Perimeter: 31.4159
Area: 78.5397
Perimeter: 31.4159
Difference Between Virtual and Pure Virtual Functions
| Virtual Functions | Pure Virtual Functions |
|---|---|
Declared with virtual keyword | Declared with virtual keyword and = 0 |
| Have implementation in the base class | No implementation in the base class (must be overridden) |
| Base class is not necessarily abstract | Make the base class abstract |
| Derived classes can choose to override | Derived classes must implement to be instantiable |
| Can be called directly | Cannot be called without an implementation |
Interface in C++
C++ doesn’t have a dedicated interface keyword like Java or C#, but pure virtual functions are used to create interface-like abstract classes. An interface in C++ is typically an abstract class with:
- Only pure virtual functions
- No data members
- No constructors (except a virtual destructor)
// Interface
class Drawable {
public:
virtual void draw() = 0;
virtual void resize() = 0;
virtual ~Drawable() {} // Virtual destructor
};
// Another interface
class Printable {
public:
virtual void print() = 0;
virtual ~Printable() {} // Virtual destructor
};
// Class implementing multiple interfaces
class Picture : public Drawable, public Printable {
public:
void draw() override {
cout << "Drawing a picture" << endl;
}
void resize() override {
cout << "Resizing a picture" << endl;
}
void print() override {
cout << "Printing a picture" << endl;
}
};
Implementing Pure Virtual Functions in the Base Class
Although unusual, you can provide an implementation for a pure virtual function in the base class. The class is still abstract, but derived classes can call the base implementation:
class Base {
public:
virtual void show() = 0; // Pure virtual
};
// Implementation outside the class
void Base::show() {
cout << "Base implementation of pure virtual function" << endl;
}
class Derived : public Base {
public:
void show() override {
// Call the base implementation
Base::show();
cout << "Derived implementation" << endl;
}
};
int main() {
Derived d;
d.show();
return 0;
}
Output:
Base implementation of pure virtual function
Derived implementation
When to Use Pure Virtual Functions
Use pure virtual functions when:
- You want to define an interface that derived classes must implement.
- The base class doesn’t have a meaningful implementation for a method.
- You want to force derived classes to provide an implementation.
- You want to prevent instantiation of the base class.
Real-World Example: Shape Hierarchy
One of the most common examples of pure virtual functions is a shape hierarchy:
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
// Abstract base class
class Shape {
public:
// Pure virtual functions
virtual double area() = 0;
virtual double perimeter() = 0;
virtual void draw() = 0;
// Normal method
void printInfo() {
cout << "Shape Info:" << endl;
cout << "- Area: " << area() << endl;
cout << "- Perimeter: " << perimeter() << endl;
draw();
cout << endl;
}
// Virtual destructor
virtual ~Shape() {}
};
// Concrete class: Circle
class Circle : public Shape {
private:
double radius;
double x, y; // Center coordinates
public:
Circle(double r, double cx, double cy) : radius(r), x(cx), y(cy) {}
double area() override {
return M_PI * radius * radius;
}
double perimeter() override {
return 2 * M_PI * radius;
}
void draw() override {
cout << "- Drawing Circle at (" << x << ", " << y << ") with radius " << radius << endl;
}
};
// Concrete class: Rectangle
class Rectangle : public Shape {
private:
double length;
double width;
double x, y; // Top-left corner coordinates
public:
Rectangle(double l, double w, double cx, double cy) : length(l), width(w), x(cx), y(cy) {}
double area() override {
return length * width;
}
double perimeter() override {
return 2 * (length + width);
}
void draw() override {
cout << "- Drawing Rectangle at (" << x << ", " << y << ") with length "
<< length << " and width " << width << endl;
}
};
// Concrete class: Triangle
class Triangle : public Shape {
private:
double a, b, c; // Sides
double x, y; // Position
public:
Triangle(double side1, double side2, double side3, double px, double py)
: a(side1), b(side2), c(side3), x(px), y(py) {}
double area() override {
// Using Heron's formula
double s = (a + b + c) / 2;
return sqrt(s * (s - a) * (s - b) * (s - c));
}
double perimeter() override {
return a + b + c;
}
void draw() override {
cout << "- Drawing Triangle at (" << x << ", " << y << ") with sides "
<< a << ", " << b << ", " << c << endl;
}
};
int main() {
vector<Shape*> shapes;
shapes.push_back(new Circle(5, 0, 0));
shapes.push_back(new Rectangle(4, 6, 10, 10));
shapes.push_back(new Triangle(3, 4, 5, 20, 20));
for (const auto& shape : shapes) {
shape->printInfo();
}
// Clean up memory
for (auto& shape : shapes) {
delete shape;
}
return 0;
}
Output:
Shape Info:
- Area: 78.5398
- Perimeter: 31.4159
- Drawing Circle at (0, 0) with radius 5
Shape Info:
- Area: 24
- Perimeter: 20
- Drawing Rectangle at (10, 10) with length 4 and width 6
Shape Info:
- Area: 6
- Perimeter: 12
- Drawing Triangle at (20, 20) with sides 3, 4, 5
Abstract Classes vs. Interfaces
In object-oriented design, there’s a distinction between abstract classes and interfaces:
-
Abstract Class:
- May contain both implemented methods and pure virtual methods
- May have data members
- Represents an “is-a” relationship
- Example:
Shapewith some common functionality
-
Interface (in C++, an abstract class with only pure virtual functions):
- Contains only pure virtual methods (no implementation)
- No data members
- Represents a “can-do” relationship
- Example:
DrawableorPrintable
Multiple Inheritance with Pure Virtual Functions
C++ allows multiple inheritance, which can be very powerful when combined with abstract classes:
#include <iostream>
using namespace std;
// First interface
class Drawable {
public:
virtual void draw() = 0;
virtual ~Drawable() {}
};
// Second interface
class Storable {
public:
virtual void save() = 0;
virtual void load() = 0;
virtual ~Storable() {}
};
// Concrete class implementing both interfaces
class Document : public Drawable, public Storable {
private:
string name;
public:
Document(const string& n) : name(n) {}
void draw() override {
cout << "Drawing document: " << name << endl;
}
void save() override {
cout << "Saving document: " << name << endl;
}
void load() override {
cout << "Loading document: " << name << endl;
}
};
int main() {
Document doc("Report.pdf");
// Use as Drawable
Drawable* d = &doc;
d->draw();
// Use as Storable
Storable* s = &doc;
s->save();
s->load();
return 0;
}
Output:
Drawing document: Report.pdf
Saving document: Report.pdf
Loading document: Report.pdf
Common Design Patterns Using Pure Virtual Functions
- Strategy Pattern: Define a family of algorithms, encapsulate each one, and make them interchangeable.
// Strategy interface
class SortStrategy {
public:
virtual void sort(int arr[], int size) = 0;
virtual ~SortStrategy() {}
};
// Concrete strategies
class BubbleSort : public SortStrategy {
public:
void sort(int arr[], int size) override {
cout << "Sorting array using bubble sort" << endl;
// Bubble sort implementation...
}
};
class QuickSort : public SortStrategy {
public:
void sort(int arr[], int size) override {
cout << "Sorting array using quick sort" << endl;
// Quick sort implementation...
}
};
// Context
class Sorter {
private:
SortStrategy* strategy;
public:
Sorter(SortStrategy* s) : strategy(s) {}
void setStrategy(SortStrategy* s) {
strategy = s;
}
void performSort(int arr[], int size) {
strategy->sort(arr, size);
}
};
- Observer Pattern: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
// Observer interface
class Observer {
public:
virtual void update(const string& message) = 0;
virtual ~Observer() {}
};
// Subject
class Subject {
private:
vector<Observer*> observers;
public:
void addObserver(Observer* obs) {
observers.push_back(obs);
}
void removeObserver(Observer* obs) {
// Remove logic...
}
void notifyObservers(const string& message) {
for (auto& obs : observers) {
obs->update(message);
}
}
};
// Concrete observer
class ConcreteObserver : public Observer {
private:
string name;
public:
ConcreteObserver(const string& n) : name(n) {}
void update(const string& message) override {
cout << name << " received: " << message << endl;
}
};
Best Practices for Pure Virtual Functions
-
Use virtual destructors: If your class has any virtual functions, always make the destructor virtual.
-
Pure virtual destructors: You can have a pure virtual destructor, but you must provide an implementation.
class Base {
public:
virtual ~Base() = 0; // Pure virtual destructor
};
Base::~Base() {
// Implementation is required
cout << "Base destructor" << endl;
}
-
Use
overridekeyword: Always use theoverridekeyword when implementing pure virtual functions to catch errors at compile time. -
Keep interfaces small: Design interfaces with the minimum number of methods needed. This follows the Interface Segregation Principle.
-
Consider default implementations: If a method will be implemented the same way in most derived classes, consider making it virtual (not pure) with a default implementation.
Summary
Pure virtual functions are a powerful feature in C++ that allow you to:
- Create abstract base classes that can’t be instantiated
- Define interfaces that derived classes must implement
- Build flexible class hierarchies
- Enable runtime polymorphism
By using pure virtual functions, you can design clean, extendable, and maintainable object-oriented systems where the base classes define the interface, and the derived classes provide the specific implementations.