Define a contract without a complete implementation
An abstract class has at least one pure virtual function for which the class has no final non-pure override. It cannot be instantiated directly. Derived classes implement the required operations and can then be used through the shared interface.
Example: shapes with different area calculations
#include
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0;
};
class Rectangle : public Shape {
double width_, height_;
public:
Rectangle(double width, double height)
: width_{width}, height_{height} { }
double area() const override { return width_ * height_; }
};
void report(const Shape& shape) {
std::cout << "Area: " << shape.area() << '\n';
}
int main() {
Rectangle rectangle{3.0, 4.0};
report(rectangle);
} Expected output: Area: 12. The report function does not need to know the rectangle's representation. It relies on the area contract. This small example assumes positive finite dimensions; add constructor validation before using untrusted values.
What = 0 means
The notation = 0 marks a pure virtual function. It is not an assignment of a numeric result. A derived class remains abstract if it leaves an inherited pure virtual function unimplemented. The override must match the required signature, including const qualifications.
Abstract classes can contain state
An abstract class may define constructors, ordinary member functions and data members. A minimal interface class often avoids shared state, but abstraction does not require an empty class. Keep shared operations focused so implementations can honor the same contract.
Destructors and ownership
A public virtual destructor is appropriate when a base interface may own derived instances through base pointers. A pure virtual destructor is also possible, but it still needs a definition; ordinary member cleanup and base destruction must run. The default virtual destructor used above is simpler for this introductory interface.
Design a useful contract
Specify units, valid input and error handling. For Shape::area, decide whether dimensions are metres and area is square metres, whether zero-sized shapes are accepted, and how invalid dimensions are rejected. Two implementations with the same signature can still be incompatible if they use different units.
Exercises
- Add a Square implementation and report a square with side 5. Expected area: 25.
- Try declaring
Shape shape;; explain why the compiler refuses. - Design a Printable interface with a const print method.
- Omit const from a derived area method while retaining override and use the diagnostic to locate the mismatch.