C++ Pure Virtual Functions: Complete Guide with Examples
C++ Pure Virtual Functions: Complete Guide with Examples
Pure virtual functions create abstract classes in C++, defining interfaces that derived classes must implement. This guide covers all aspects of pure virtual functions with practical examples.
What are Pure Virtual Functions?
A pure virtual function is a virtual function declared in a base class with no implementation (using = 0). Classes containing pure virtual functions are abstract classes and cannot be instantiated directly.
Basic Syntax
class AbstractBase {
public:
// Pure virtual function
virtual void pureFunc() = 0;
// Regular virtual function (has implementation)
virtual void regularFunc() {
std::cout << "Default implementation" << std::endl;
}
virtual ~AbstractBase() = default;
};
Basic Example
#include <iostream>
class Shape {
public:
// Pure virtual function - must be implemented by derived classes
virtual double area() const = 0;
// Pure virtual function
virtual void draw() const = 0;
// Regular virtual function with default implementation
virtual void printInfo() const {
std::cout << "Shape information" << std::endl;
}
virtual ~Shape() = default;
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) { }
// Must implement pure virtual functions
double area() const override {
return 3.14159 * radius * radius;
}
void draw() const override {
std::cout << "Drawing circle with radius " << radius << std::endl;
}
};
int main() {
// Shape shape; // ❌ Error: cannot instantiate abstract class
Circle circle(5.0);
circle.draw(); // ✅ Works
std::cout << "Area: " << circle.area() << std::endl;
Shape* shapePtr = &circle; // ✅ Can use pointer/reference
shapePtr->draw();
return 0;
}
Abstract Classes
A class with at least one pure virtual function is an abstract class:
- Cannot be instantiated directly
- Can be used as pointers or references
- Derived classes must implement all pure virtual functions to be concrete
Example: Abstract Animal Class
#include <iostream>
#include <string>
class Animal {
public:
// Pure virtual functions - must be implemented
virtual void makeSound() const = 0;
virtual void move() const = 0;
virtual std::string getName() const = 0;
// Regular virtual function with default
virtual void sleep() const {
std::cout << getName() << " is sleeping" << std::endl;
}
virtual ~Animal() = default;
};
class Dog : public Animal {
private:
std::string name;
public:
Dog(const std::string& n) : name(n) { }
// Implement all pure virtual functions
void makeSound() const override {
std::cout << name << " says: Woof! Woof!" << std::endl;
}
void move() const override {
std::cout << name << " runs on four legs" << std::endl;
}
std::string getName() const override {
return name;
}
};
class Bird : public Animal {
private:
std::string name;
public:
Bird(const std::string& n) : name(n) { }
void makeSound() const override {
std::cout << name << " says: Chirp! Chirp!" << std::endl;
}
void move() const override {
std::cout << name << " flies" << std::endl;
}
std::string getName() const override {
return name;
}
};
int main() {
// Animal animal; // ❌ Error: abstract class cannot be instantiated
Dog dog("Buddy");
Bird bird("Tweety");
Animal* animals[] = {&dog, &bird};
for (Animal* animal : animals) {
animal->makeSound();
animal->move();
animal->sleep(); // Uses default implementation
}
return 0;
}
Interfaces in C++
C++ doesn’t have a separate interface keyword like Java or C#, but you can create interface-like classes using pure virtual functions:
Interface Pattern
// Interface: all functions are pure virtual
class IReadable {
public:
virtual std::string read() const = 0;
virtual ~IReadable() = default;
};
class IWritable {
public:
virtual void write(const std::string& data) = 0;
virtual ~IWritable() = default;
};
// Class implementing multiple interfaces
class File : public IReadable, public IWritable {
private:
std::string filename;
public:
File(const std::string& name) : filename(name) { }
std::string read() const override {
return "Reading from " + filename;
}
void write(const std::string& data) override {
std::cout << "Writing to " << filename << ": " << data << std::endl;
}
};
int main() {
File file("data.txt");
IReadable* readable = &file;
IWritable* writable = &file;
std::cout << readable->read() << std::endl;
writable->write("Hello, World!");
return 0;
}
Pure Virtual Functions with Implementation
In C++, you can provide an implementation for a pure virtual function, but the class remains abstract:
class Base {
public:
// Pure virtual with implementation
virtual void func() = 0;
};
// Implementation can be provided
void Base::func() {
std::cout << "Base::func() implementation" << std::endl;
}
class Derived : public Base {
public:
void func() override {
Base::func(); // Can call base implementation
std::cout << "Derived::func() additional work" << std::endl;
}
};
int main() {
// Base base; // ❌ Still abstract, cannot instantiate
Derived derived;
derived.func();
return 0;
}
Pure Virtual Destructor
A destructor can be pure virtual, but it must have a definition:
class AbstractBase {
public:
virtual ~AbstractBase() = 0; // Pure virtual destructor
};
// Must provide definition (unlike other pure virtual functions)
AbstractBase::~AbstractBase() {
std::cout << "AbstractBase destructor" << std::endl;
}
class Derived : public AbstractBase {
public:
~Derived() override {
std::cout << "Derived destructor" << std::endl;
}
};
int main() {
AbstractBase* ptr = new Derived();
delete ptr; // ✅ Both destructors called
return 0;
}
Practical Examples
Example 1: Graphics System
#include <iostream>
#include <vector>
#include <cmath>
class Drawable {
public:
virtual void draw() const = 0;
virtual double getArea() const = 0;
virtual ~Drawable() = default;
};
class Circle : public Drawable {
private:
double x, y, radius;
public:
Circle(double x, double y, double r) : x(x), y(y), radius(r) { }
void draw() const override {
std::cout << "Drawing circle at (" << x << ", " << y
<< ") with radius " << radius << std::endl;
}
double getArea() const override {
return 3.14159 * radius * radius;
}
};
class Rectangle : public Drawable {
private:
double x, y, width, height;
public:
Rectangle(double x, double y, double w, double h)
: x(x), y(y), width(w), height(h) { }
void draw() const override {
std::cout << "Drawing rectangle at (" << x << ", " << y
<< ") with size " << width << "x" << height << std::endl;
}
double getArea() const override {
return width * height;
}
};
int main() {
std::vector<Drawable*> shapes;
shapes.push_back(new Circle(0, 0, 5));
shapes.push_back(new Rectangle(10, 10, 4, 6));
shapes.push_back(new Circle(20, 20, 3));
for (Drawable* shape : shapes) {
shape->draw();
std::cout << "Area: " << shape->getArea() << std::endl;
}
// Cleanup
for (Drawable* shape : shapes) {
delete shape;
}
return 0;
}
Example 2: Plugin System
#include <iostream>
#include <string>
#include <vector>
class Plugin {
public:
virtual void initialize() = 0;
virtual void execute() = 0;
virtual void cleanup() = 0;
virtual std::string getName() const = 0;
virtual ~Plugin() = default;
};
class CalculatorPlugin : public Plugin {
public:
void initialize() override {
std::cout << "Calculator plugin initialized" << std::endl;
}
void execute() override {
std::cout << "Calculator: 2 + 2 = 4" << std::endl;
}
void cleanup() override {
std::cout << "Calculator plugin cleaned up" << std::endl;
}
std::string getName() const override {
return "Calculator";
}
};
class LoggerPlugin : public Plugin {
public:
void initialize() override {
std::cout << "Logger plugin initialized" << std::endl;
}
void execute() override {
std::cout << "Logger: Logging message" << std::endl;
}
void cleanup() override {
std::cout << "Logger plugin cleaned up" << std::endl;
}
std::string getName() const override {
return "Logger";
}
};
class PluginManager {
private:
std::vector<Plugin*> plugins;
public:
void registerPlugin(Plugin* plugin) {
plugins.push_back(plugin);
}
void initializeAll() {
for (Plugin* plugin : plugins) {
plugin->initialize();
}
}
void executeAll() {
for (Plugin* plugin : plugins) {
std::cout << "Executing " << plugin->getName() << ":" << std::endl;
plugin->execute();
}
}
void cleanupAll() {
for (Plugin* plugin : plugins) {
plugin->cleanup();
}
}
~PluginManager() {
for (Plugin* plugin : plugins) {
delete plugin;
}
}
};
int main() {
PluginManager manager;
manager.registerPlugin(new CalculatorPlugin());
manager.registerPlugin(new LoggerPlugin());
manager.initializeAll();
manager.executeAll();
manager.cleanupAll();
return 0;
}
Example 3: Strategy Pattern
#include <iostream>
#include <vector>
class SortingStrategy {
public:
virtual void sort(std::vector<int>& data) = 0;
virtual ~SortingStrategy() = default;
};
class BubbleSort : public SortingStrategy {
public:
void sort(std::vector<int>& data) override {
std::cout << "Using Bubble Sort" << std::endl;
// Bubble sort implementation
for (size_t i = 0; i < data.size(); ++i) {
for (size_t j = 0; j < data.size() - i - 1; ++j) {
if (data[j] > data[j + 1]) {
std::swap(data[j], data[j + 1]);
}
}
}
}
};
class QuickSort : public SortingStrategy {
public:
void sort(std::vector<int>& data) override {
std::cout << "Using Quick Sort" << std::endl;
// Quick sort implementation (simplified)
std::sort(data.begin(), data.end());
}
};
class Sorter {
private:
SortingStrategy* strategy;
public:
Sorter(SortingStrategy* s) : strategy(s) { }
void setStrategy(SortingStrategy* s) {
strategy = s;
}
void performSort(std::vector<int>& data) {
strategy->sort(data);
}
};
int main() {
std::vector<int> data = {64, 34, 25, 12, 22, 11, 90};
BubbleSort bubbleSort;
QuickSort quickSort;
Sorter sorter(&bubbleSort);
sorter.performSort(data);
data = {64, 34, 25, 12, 22, 11, 90};
sorter.setStrategy(&quickSort);
sorter.performSort(data);
return 0;
}
Common Mistakes
Mistake 1: Trying to Instantiate Abstract Class
class Abstract {
public:
virtual void func() = 0;
};
int main() {
Abstract obj; // ❌ Error: cannot instantiate abstract class
return 0;
}
Solution: Only instantiate concrete derived classes.
Mistake 2: Not Implementing All Pure Virtual Functions
class Base {
public:
virtual void func1() = 0;
virtual void func2() = 0;
};
class Derived : public Base {
public:
void func1() override { } // ❌ Missing func2() - still abstract
};
int main() {
Derived obj; // ❌ Error: Derived is still abstract
return 0;
}
Solution: Implement all pure virtual functions to make class concrete.
Mistake 3: Forgetting Virtual Destructor
class Base {
public:
virtual void func() = 0;
// ❌ Missing virtual destructor
~Base() { }
};
Solution: Always use virtual destructor in abstract base classes.
Best Practices
- Use pure virtual functions to define interfaces
- Always provide virtual destructor in abstract classes
- Document pure virtual functions that must be implemented
- Provide default implementations for optional behavior using regular virtual functions
- Use pure virtual destructors when you want abstract class but need destructor definition
- Implement all pure virtual functions in derived classes to make them concrete
- Use interfaces (all pure virtual) for maximum flexibility
Summary
Pure virtual functions are essential for creating abstract classes and interfaces:
- Pure virtual functions (
= 0) create abstract classes - Abstract classes cannot be instantiated directly
- Derived classes must implement all pure virtual functions to be concrete
- Use for interfaces and defining contracts
- Always use virtual destructors in abstract classes
- Follow best practices for clean, maintainable code
Pure virtual functions enable powerful design patterns and clean architecture in C++.