SOLID principles in C++: from theory to practice
SOLID is an acronym for five fundamental principles of object-oriented programming formulated by Robert Martin (Uncle Bob). In the context of C++, these principles help create flexible, maintainable, and testable code. In this article, we will break down each principle with practical examples in C++.
1. Single Responsibility Principle (SRP)
Essence: Each class should have only one reason to change. Simply put — a class handles only one task.
Bad example (SRP violation):
class Report {public: void generateReport() { /* generate report */ } void saveToFile(const std::string& filename) { /* save to file */ } void printReport() { /* print report */ }};Here the Report class is responsible for generation, saving, and printing — three different responsibilities.
Good example (SRP compliance):
class Report {public: std::string generate() { /* generate report */ return "report data"; }};
class ReportSaver {public: void save(const std::string& data, const std::string& filename) { // save to file }};
class ReportPrinter {public: void print(const std::string& data) { // print report }};Now each class has one clear responsibility, which simplifies testing and modification.
2. Open-Closed Principle (OCP)
Essence: Classes should be open for extension but closed for modification. You should be able to add new functionality without changing existing code.
Bad example:
enum class ShapeType { Circle, Rectangle };
class Shape {public: ShapeType type;};
class AreaCalculator {public: double calculateArea(const Shape& shape) { if (shape.type == ShapeType::Circle) { // calculate circle area return 3.14 * radius * radius; } else if (shape.type == ShapeType::Rectangle) { // calculate rectangle area return width * height; } return 0.0; }};When adding a new shape (e.g., triangle), you would have to modify AreaCalculator.
Good example (via polymorphism):
class Shape {public: virtual double area() const = 0; virtual ~Shape() = default;};
class Circle : public Shape { double radius;public: explicit Circle(double r) : radius(r) {} double area() const override { return 3.14159 * radius * radius; }};
class Rectangle : public Shape { double width, height;public: Rectangle(double w, double h) : width(w), height(h) {} double area() const override { return width * height; }};
class AreaCalculator {public: double calculateArea(const Shape& shape) { return shape.area(); }};Now to add a new shape, you only need to create a new derived class without touching AreaCalculator.
3. Liskov Substitution Principle (LSP)
Essence: Objects of subclasses should be interchangeable with objects of the base class without breaking program correctness.
Classic violation (rectangle-square):
class Rectangle {protected: int width, height;public: void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } int area() const { return width * height; }};
class Square : public Rectangle {public: void setWidth(int w) override { width = w; height