Design Patterns in C++ with Examples: Complete Breakdown

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 Design Patterns are Needed in C++



Design patterns are proven solutions to typical problems that arise during software development. In C++, with its powerful type system, RAII, and multiple inheritance, patterns gain special flexibility. They help make code more readable, maintainable, and scalable.



In this article, we will look at 4 key patterns from the GoF (Gang of Four) catalog: Singleton, Factory Method, Observer, and Strategy. Each pattern will be illustrated with a real code example in C++17/20.



Creational Patterns: Singleton



Singleton guarantees that a class has only one instance and provides a global access point to it. In C++, the modern approach uses a local static variable (thread-safe since C++11).



Example: Application Logger



class Logger {public:    // Delete copy and assignment    Logger(const Logger&) = delete;    Logger& operator=(const Logger&) = delete;

static Logger& getInstance() { static Logger instance; // thread-safe since C++11 return instance; }

void log(const std::string& message) { std::lock_guard<std::mutex> lock(m_mutex); std::cout << "[LOG] " << message << std::endl; }

private: Logger() = default; // private constructor ~Logger() = default; std::mutex m_mutex;};

// Usageint main() { Logger::getInstance().log("Application started"); Logger::getInstance().log("Error: file not found"); return 0;}


Pros: guaranteed uniqueness, lazy initialization, thread safety.
Cons: complicates testing (global state), violates the single responsibility principle.



Creational Patterns: Factory Method



Factory Method defines an interface for creating an object but lets subclasses decide which class to instantiate. In C++, it is often implemented through virtual functions and smart pointers.



Example: Generating Different Document Types



// Abstract productclass Document {public:    virtual void open() = 0;    virtual void save() = 0;    virtual ~Document() = default;};

// Concrete productsclass PDFDocument : public Document {public: void open() override { std::cout << "PDF opened"; } void save() override { std::cout << "PDF saved"; }};

class WordDocument : public Document {public: void open() override { std::cout << "Word opened"; } void save() override { std::cout << "Word saved"; }};

// Creatorclass Application {public: virtual std::unique_ptr<Document> createDocument() = 0; virtual ~Application() = default;

void newDocument() { auto doc = createDocument(); doc->open(); // work with document }};

// Concrete creatorsclass PDFApplication : public Application {public: std::unique_ptr<Document> createDocument() override { return std::make_unique<PDFDocument>(); }};

class WordApplication : public Application {public: std::unique_ptr<Document> createDocument() override { return std::make_unique<WordDocument>(); }};

// Usageint main() { std::unique_ptr<Application> app = std::make_unique<PDFApplication>(); app->newDocument(); // Output: "PDF opened" return 0;}


This pattern is ideal when the exact types of objects to be created are not known in advance.



Behavioral Patterns: Observer



Blogs

Book Recommendations