SOLID Principles in C++: From Theory to Practice for Clean Code

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

Introduction: Why SOLID is Important for a C++ Developer?

\n\n

SOLID is an acronym for five fundamental principles of object-oriented programming, formulated by Robert Martin (Uncle Bob). For a C++ developer, these principles are not just abstract theory, but a practical tool that helps create flexible, maintainable, and testable applications. In the world of C++, where memory management and performance are critical, following SOLID helps avoid "spaghetti code" and simplifies refactoring.

\n\n

In this article, we will break down each SOLID principle with concrete examples in C++. You will see how violating the principles leads to problems and how to fix them. We will use modern C++ (C++11/14/17/20) to keep the examples relevant.

\n\n

1. Single Responsibility Principle (SRP)

\n\n

Essence of the Principle

\n

A class should have only one reason to change. In other words, each class is responsible for only one functionality or aspect of the system.

\n\n

SRP Violation in C++

\n

Consider a class that violates SRP: it simultaneously manages report data and its formatting.

\n\n
class Report {\npublic:\n    void generateReport(const std::vector<Data>& data) {\n        // Data processing logic\n        m_data = data;\n    }\n    \n    void saveToFile(const std::string& filename) {\n        // File saving logic\n        std::ofstream file(filename);\n        for (const auto& item : m_data) {\n            file << item.toString() << "\\n";\n        }\n    }\n    \n    void printToConsole() {\n        // Console output logic\n        for (const auto& item : m_data) {\n            std::cout << item.toString() << std::endl;\n        }\n    }\n    \nprivate:\n    std::vector<Data> m_data;\n};
\n\n

Problem: if the output format changes, you have to change the Report class. If the data storage method changes — also. This violates SRP.

\n\n

Fixing It: Separating Responsibilities

\n\n
// Responsible only for report data\nclass ReportData {\npublic:\n    void addData(const Data& item) {\n        m_data.push_back(item);\n    }\n    \n    const std::vector<Data>& getData() const {\n        return m_data;\n    }\n    \nprivate:\n    std::vector<Data> m_data;\n};\n\n// Responsible only for saving\nclass FileSaver {\npublic:\n    void save(const std::string& filename, const ReportData& report) {\n        std::ofstream file(filename);\n        for (const auto& item : report.getData()) {\n            file << item.toString() << "\\n";\n        }\n    }\n};\n\n// Responsible only for console output\nclass ConsolePrinter {\npublic:\n    void print(const ReportData& report) {\n        for (const auto& item : report.getData()) {\n            std::cout << item.toString() << std::endl;\n        }\n    }\n};
\n\n

Now each class has one reason to change, making the code more flexible and easier to test.

\n\n

2. Open-Closed Principle (OCP)

\n\n

Essence of the Principle

\n

Software entities (classes, modules, functions) should be open for extension but closed for modification. This means you should be able to add new functionality without changing existing code.

\n\n

OCP Violation in C++

\n

Imagine a discount calculation system that uses if-else for each customer type.

\n\n
class DiscountCalculator {\npublic:\n    double calculateDiscount(const std::string& customerType, double amount) {\n        if (customerType == "regular") {\n            return amount * 0.1; // 10%\n        } else if (customerType == "vip") {\n            return amount * 0.2; // 20%\n        } else if (customerType == "premium") {\n            return amount * 0.3; // 30%\n        }\n                

Blogs

Book Recommendations