Design Patterns in C++ with Examples: From A to Z
Design patterns are proven solutions to common problems that arise during software development. For C++, these patterns are especially important because the language combines object-oriented, generic, and low-level programming. In this article, we will break down key design patterns in C++ with code examples that will help you write more flexible, reusable, and maintainable code.
We will cover three main categories of patterns according to the GoF (Gang of Four) classification: creational, structural, and behavioral. Each pattern will be illustrated with a real-world example in C++.
1. Creational Patterns
Creational patterns abstract the object instantiation process, making a system independent of how its objects are created, composed, and represented.
1.1. Singleton
Singleton ensures that a class has only one instance and provides a global point of access to it. In C++, it is often implemented using a static local object (thread-safe since C++11).
class Logger {public: static Logger& getInstance() { static Logger instance; // thread-safe initialization return instance; }
void log(const std::string& message) { std::cout << "[LOG] " << message << std::endl; }
private: Logger() = default; Logger(const Logger&) = delete; Logger& operator=(const Logger&) = delete;};
// Usageint main() { Logger::getInstance().log("Application started"); return 0;}When to use: configuration management, thread pools, logging systems.
1.2. Factory Method
Factory Method defines an interface for creating an object but lets subclasses decide which class to instantiate.
class Product {public: virtual void use() = 0; virtual ~Product() = default;};
class ConcreteProductA : public Product {public: void use() override { std::cout << "Product A" << std::endl; }};
class ConcreteProductB : public Product {public: void use() override { std::cout << "Product B" << std::endl; }};
class Creator {public: virtual Product* createProduct() = 0; void someOperation() { Product* p = createProduct(); p->use(); delete p; } virtual ~Creator() = default;};
class CreatorA : public Creator {public: Product* createProduct() override { return new ConcreteProductA(); }};
class CreatorB : public Creator {public: Product* createProduct() override { return new ConcreteProductB(); }};When to use: when a class does not know which subclass objects it needs to create.
2. Structural Patterns
Structural patterns describe how classes and objects can be combined into larger structures while maintaining flexibility and efficiency.
2.1. Adapter
Adapter allows objects with incompatible interfaces to work together. In C++, it is often implemented through inheritance or composition.
// Target interfaceclass Target {public: virtual void request() const { std::cout << "Normal request" << std::endl; } virtual ~Target() = default;};
// Adaptee classclass Adaptee {public: void specificRequest() const { std::cout << "Specific request" << std::endl; }};
// Adapter (via composition)class Adapter : public Target {private: Adaptee* adaptee_;public: Adapter(Adaptee* a) : adaptee_(a) {} void request() const override { adaptee_->specificRequest(); }};