SOLID Principles in C++: Theory and Practice with Code Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

SOLID principles in C++: from theory to practice



SOLID principles are five fundamental rules of object-oriented design formulated by Robert Martin. They help create flexible, scalable, and easily maintainable code. In this article, we will break down each principle using C++ examples, from simple theory to real-world practical cases.



1. Single Responsibility Principle (SRP)



Theory: Each class should have only one reason to change. In other words, a class is responsible for only one part of the program's functionality.



Problem: Developers often place data logic, saving, and screen output into a single class. This violates SRP.



Practice in C++: Let's split the Report class into three separate classes.



// Bad: violates SRPclass Report {    std::string title;    std::string content;public:    void generate() { /* report generation */ }    void saveToFile(const std::string& filename) { /* saving to file */ }    void print() { /* printing */ }};

// Good: complies with SRPclass Report { std::string title; std::string content;public: void generate() { /* generation only */ }};

class ReportSaver {public: void saveToFile(const Report& report, const std::string& filename) { /* saving */ }};

class ReportPrinter {public: void print(const Report& report) { /* printing */ }};


Conclusion: Each class is now responsible for its own task. The code has become easier to test and modify.



2. Open-Closed Principle (OCP)



Theory: Software entities (classes, modules, functions) should be open for extension but closed for modification. That is, we should be able to add new functionality without changing existing code.



Practice in C++: Use polymorphism and abstract classes.



#include <iostream>#include <vector>#include <memory>

// Base class for shapesclass Shape {public: virtual double area() const = 0; virtual ~Shape() = default;};

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 Circle : public Shape { double radius;public: Circle(double r) : radius(r) {} double area() const override { return 3.14159 * radius * radius; }};

// Class for calculating total area — does not require changes when adding new shapesclass AreaCalculator {public: double totalArea(const std::vector<std::unique_ptr<Shape>>& shapes) { double total = 0; for (const auto& shape : shapes) { total += shape->area(); } return total; }};

int main() { std::vector<std::unique_ptr<Shape>> shapes; shapes.push_back(std::make_unique<Rectangle>(4, 5)); shapes.push_back(std::make_unique<Circle>(3));

AreaCalculator calc; std::cout << "Total area: " << calc.totalArea(shapes) << std::endl; return 0;}


Conclusion: We can add new shapes (e.g., Triangle) without touching AreaCalculator.



3. Liskov Substitution Principle (LSP)



Theory: Objects in a program should be replaceable with instances of their subtypes without altering the correctness of the program. Simply put, if you have a parent class and a child class, you should be able to use the child where the parent is expected, and everything should work.



Practice

Blogs

Book Recommendations