C++ Interfaces
Interfaces in C++ (Abstract Classes)
Section titled “Interfaces in C++ (Abstract Classes)”In object-oriented programming, an interface defines a contract for a class. It specifies what a class can do (its capabilities or methods) without dictating how it does it. This promotes loose coupling and polymorphism.
C++ implements interfaces using abstract classes. An abstract class is a class that cannot be instantiated directly and is intended to be used as a base class for derived classes.
A class becomes abstract if it declares at least one pure virtual function. A pure virtual function is a virtual function that is declared but not necessarily defined in the base class. It’s declared by appending = 0 to its signature:
#include <string>
// Abstract base class defining an 'Drawable' interfaceclass Drawable {public: // Pure virtual function - derived classes MUST implement this virtual void draw() const = 0;
// Abstract classes can still have regular members and methods virtual std::string getName() const { return "Generic Drawable"; }
// IMPORTANT: Abstract base classes used polymorphically need a virtual destructor virtual ~Drawable() = default; // Use default implementation};Key points about abstract classes:
- Cannot Instantiate: You cannot create objects of an abstract class type directly (
Drawable d;would cause a compile error). - Interface Role: They serve primarily as interfaces, defining a common set of functions that derived classes must implement.
- Polymorphism: They are typically used with pointers or references to allow treating objects of different derived classes through the common base class interface.
- Mandatory Implementation: Any concrete (non-abstract) class deriving from an abstract class must provide implementations for all inherited pure virtual functions.
- Virtual Destructor: If you intend to delete derived objects through a base class pointer (
Drawable* ptr = new Circle(); delete ptr;), the base class must have a virtual destructor. Otherwise, only the base class destructor runs, leading to resource leaks (Undefined Behavior). Addingvirtual ~ClassName() = default;is often sufficient.
Concrete Classes:
Section titled “Concrete Classes:”A class that can be instantiated (i.e., is not abstract) is called a concrete class. A concrete class derived from an abstract class provides implementations for all inherited pure virtual functions.
Abstract Class Example:
Section titled “Abstract Class Example:”Let’s implement the Drawable interface with concrete Circle and Square classes:
#include <iostream>#include <string>#include <vector>#include <memory> // For std::unique_ptr
// Abstract base class (Interface)class Shape {public: // Pure virtual function - defines the interface contract virtual double getArea() const = 0; virtual std::string getName() const = 0;
// Virtual destructor is crucial for base classes with virtual functions virtual ~Shape() { std::cout << "Destroying Shape\n"; }; // Example non-default destructor
// Common utility function (optional) void displayInfo() const { std::cout << "Shape Type: " << getName() << ", Area: " << getArea() << std::endl; }
protected: // Protected constructor if needed, but Shape cannot be instantiated directly Shape() = default;};
// Concrete derived class: Rectangleclass Rectangle : public Shape {public: Rectangle(double w, double h) : width(w), height(h) {}
// Override pure virtual functions double getArea() const override { return width * height; } std::string getName() const override { return "Rectangle"; }
~Rectangle() override { std::cout << "Destroying Rectangle\n"; }
private: double width; double height;};
// Concrete derived class: Triangleclass Triangle : public Shape {public: Triangle(double b, double h) : base(b), height(h) {}
// Override pure virtual functions double getArea() const override { return 0.5 * base * height; } std::string getName() const override { return "Triangle"; }
~Triangle() override { std::cout << "Destroying Triangle\n"; }
private: double base; double height;};
int main() { // Using unique_ptr for automatic memory management (RAII) std::vector<std::unique_ptr<Shape>> shapes; shapes.push_back(std::make_unique<Rectangle>(5.0, 7.0)); shapes.push_back(std::make_unique<Triangle>(5.0, 7.0));
std::cout << "Processing shapes polymorphically:\n"; for (const auto& shapePtr : shapes) { // Calls the correct getArea/getName based on the actual object type shapePtr->displayInfo(); }
std::cout << "\nShapes vector going out of scope...\n"; // unique_ptr automatically calls delete on the contained pointers, // invoking the correct derived destructors thanks to the virtual base destructor. return 0;}When the above code is compiled and executed, it produces output similar to:
Processing shapes polymorphically:Shape Type: Rectangle, Area: 35Shape Type: Triangle, Area: 17.5
Shapes vector going out of scope...Destroying RectangleDestroying ShapeDestroying TriangleDestroying ShapeNotice how we can treat Rectangle and Triangle objects uniformly through the Shape pointer. The override keyword helps ensure we are correctly implementing the virtual functions from the base class. The virtual destructor ensures proper cleanup.
Designing Strategy:
Section titled “Designing Strategy:”Using abstract classes (interfaces) is a powerful design strategy:
- Decoupling: Code that uses the interface (like the
mainfunction usingShape*) doesn’t need to know the specific concrete types (Rectangle,Triangle). It only depends on theShapeinterface. - Extensibility: You can easily add new shapes (e.g.,
Circle) by deriving fromShapeand implementing the required functions, without modifying the code that uses theShapeinterface. - Standardization: Ensures that all derived classes provide a minimum set of functionalities defined by the pure virtual functions in the base class.
Modern C++ (C++20) also introduced Concepts, which provide a way to specify requirements on template arguments at compile time, sometimes serving as an alternative or complement to traditional interfaces for generic programming.