agentleFS
ROCmROCm/rocprofiler-systems-skills/skills/programming-cpp-design-patterns/SKILL.md

programming-cpp-design-patterns

Skill5 starsChanged 17 days agoMITView on GitHub

C++ design patterns reference - suggests applicable patterns when code matches a problem that a pattern solves

The file

We split this skill into the rules it gives an agent

7 rules. About 0 are common, 7 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈7

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use this skill when implementing C++ code that could benefit from design patterns.

    line 8
  2. Distinctive:

    When you recognize a problem that matches a design pattern, suggest the pattern to the user with a brief explanation of why it fits.

    line 11
  3. Distinctive:

    When you detect a pattern opportunity, present it like this:

    line 65
  4. Distinctive:

    Use sparingly: Only when exactly one instance is truly needed (logger, config).

    line 141
  5. Distinctive:

    Singleton abuse: - Don't use for everything.

    line 475
  6. Distinctive:

    Over-engineering: - Don't add patterns "just in case"

    line 476
  7. Distinctive:

    Wrong pattern: - Make sure the problem actually matches

    line 478

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

C++ Design Patterns Skill

Use this skill when implementing C++ code that could benefit from design patterns.

<IMPORTANT> When you recognize a problem that matches a design pattern, **suggest the pattern to the user** with a brief explanation of why it fits. </IMPORTANT>

Reference: Refactoring.Guru - Design Patterns in C++

When to Suggest Patterns

Actively look for these problem indicators and suggest the matching pattern:

Creational Patterns

PatternSuggest When...
Factory MethodCode uses new with conditional logic to decide which class to instantiate
Abstract FactoryNeed to create families of related objects that must be used together
BuilderConstructor has many parameters, or object requires step-by-step construction
PrototypeNeed to copy objects without depending on their concrete classes
SingletonNeed exactly one instance with global access (use sparingly!)

Structural Patterns

PatternSuggest When...
AdapterNeed to use a class with an incompatible interface
BridgeWant to separate abstraction from implementation (vary both independently)
CompositeWorking with tree structures where leaf and composite should be treated uniformly
DecoratorNeed to add behaviors to objects dynamically without subclassing
FacadeNeed to provide a simple interface to a complex subsystem
FlyweightNeed many similar objects that share common state to save memory
ProxyNeed to control access to an object (lazy loading, access control, logging)

Behavioral Patterns

PatternSuggest When...
Chain of ResponsibilityMultiple handlers could process a request, decided at runtime
CommandNeed to parameterize objects with operations, queue operations, or support undo
IteratorNeed to traverse a collection without exposing its internal structure
MediatorMany objects communicate in complex ways, creating tight coupling
MementoNeed to save and restore object state (undo/redo functionality)
ObserverObjects need to be notified when another object changes state
StateObject behavior changes based on internal state (like a state machine)
StrategyNeed to select algorithm at runtime, or have multiple ways to do something
Template MethodAlgorithm structure is fixed but some steps vary in subclasses
VisitorNeed to add operations to objects without modifying their classes

C++ Idioms

PatternSuggest When...
Type ErasureNeed polymorphism for unrelated types without inheritance (duck typing)

How to Suggest

When you detect a pattern opportunity, present it like this:

Design Pattern Suggestion: [Pattern Name]

I notice you're [describe the problem]. This is a good fit for the [Pattern Name] pattern.

Why it fits:

  • [Reason 1]
  • [Reason 2]

Would you like me to implement it using this pattern?

Pattern Details

Creational Patterns

Factory Method

// Problem: Direct instantiation with conditional logic
if (type == "A") return new ProductA();
else return new ProductB();

// Solution: Factory Method
class Creator {
public:
    virtual std::unique_ptr<Product> create_product() = 0;
};

Use when: Subclasses should decide which class to instantiate.

Abstract Factory

// Creates families of related objects
class GUIFactory {
public:
    virtual std::unique_ptr<Button> create_button() = 0;
    virtual std::unique_ptr<Checkbox> create_checkbox() = 0;
};

Use when: System should be independent of how products are created.

Builder

// Problem: Complex constructor
Car(engine, wheels, seats, gps, sunroof, color, ...);

// Solution: Builder
auto car = CarBuilder()
    .set_engine(v8)
    .set_wheels(4)
    .set_gps(true)
    .build();

Use when: Object construction has many optional parameters or steps.

Prototype

class Prototype {
public:
    virtual std::unique_ptr<Prototype> clone() const = 0;
};

Use when: Need to copy objects without coupling to their concrete classes.

Singleton

class Singleton {
public:
    static Singleton& instance() {
        static Singleton instance;
        return instance;
    }
private:
    Singleton() = default;
};

Use sparingly: Only when exactly one instance is truly needed (logger, config).

Structural Patterns

Adapter

// Make incompatible interface work with expected interface
class Adapter : public Target {
    Adaptee* m_adaptee;
public:
    void request() override {
        m_adaptee->specific_request();
    }
};

Use when: Integrating legacy code or third-party libraries.

Bridge

// Separate abstraction from implementation
class Abstraction {
protected:
    std::unique_ptr<Implementation> m_impl;
public:
    virtual void operation() {
        m_impl->operation_impl();
    }
};

Use when: Both abstraction and implementation need to vary independently.

Composite

class Component {
public:
    virtual void operation() = 0;
    virtual void add(std::shared_ptr<Component>) {}
};

class Composite : public Component {
    std::vector<std::shared_ptr<Component>> m_children;
};

Use when: Working with tree structures (file systems, UI components, org charts).

Decorator

class Decorator : public Component {
protected:
    std::unique_ptr<Component> m_wrapped;
public:
    void operation() override {
        m_wrapped->operation();
        // Add behavior
    }
};

Use when: Need to add responsibilities dynamically (streams, middleware).

Facade

class Facade {
    SubsystemA m_a;
    SubsystemB m_b;
    SubsystemC m_c;
public:
    void simple_operation() {
        m_a.op1();
        m_b.op2();
        m_c.op3();
    }
};

Use when: Need simple interface to complex subsystem.

Flyweight

class FlyweightFactory {
    std::unordered_map<std::string, std::shared_ptr<Flyweight>> m_cache;
public:
    std::shared_ptr<Flyweight> get_flyweight(const std::string& key);
};

Use when: Many objects share common state (text characters, game particles).

Proxy

class Proxy : public Subject {
    std::unique_ptr<RealSubject> m_real;
public:
    void request() override {
        // Access control, lazy loading, logging, caching
        if (!m_real) m_real = std::make_unique<RealSubject>();
        m_real->request();
    }
};

Use when: Need lazy initialization, access control, logging, or caching.

Behavioral Patterns

Chain of Responsibility

class Handler {
    std::shared_ptr<Handler> m_next;
public:
    virtual void handle(Request& req) {
        if (m_next) m_next->handle(req);
    }
};

Use when: Multiple handlers for requests (middleware, event handling).

Command

class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
};

Use when: Need undo/redo, queuing, or scheduling operations.

Iterator

class Iterator {
public:
    virtual bool has_next() = 0;
    virtual Element& next() = 0;
};

Use when: Traversing custom collections. (Prefer STL iterators in C++)

Mediator

class Mediator {
public:
    virtual void notify(Component* sender, const std::string& event) = 0;
};

Use when: Components have complex interdependencies.

Memento

class Memento {
    State m_state;
    friend class Originator;
};

class Originator {
public:
    Memento save() { return Memento{m_state}; }
    void restore(const Memento& m) { m_state = m.m_state; }
};

Use when: Need save/restore functionality (undo, checkpoints).

Observer

class Subject {
    std::vector<Observer*> m_observers;
public:
    void attach(Observer* o) { m_observers.push_back(o); }
    void notify() {
        for (auto* o : m_observers) o->update();
    }
};

Use when: Objects need to react to changes in other objects.

State

class State {
public:
    virtual void handle(Context& ctx) = 0;
};

class Context {
    std::unique_ptr<State> m_state;
public:
    void set_state(std::unique_ptr<State> s) { m_state = std::move(s); }
    void request() { m_state->handle(*this); }
};

Use when: Object behavior depends on state (state machines).

Strategy

class Strategy {
public:
    virtual void execute() = 0;
};

class Context {
    std::unique_ptr<Strategy> m_strategy;
public:
    void set_strategy(std::unique_ptr<Strategy> s) { m_strategy = std::move(s); }
};

Use when: Need interchangeable algorithms (sorting, validation, formatting).

Template Method

class AbstractClass {
public:
    void template_method() {
        step1();
        step2();  // Override this
        step3();
    }
protected:
    virtual void step2() = 0;
};

Use when: Algorithm structure is fixed but some steps vary.

Visitor

class Visitor {
public:
    virtual void visit(ElementA& e) = 0;
    virtual void visit(ElementB& e) = 0;
};

class Element {
public:
    virtual void accept(Visitor& v) = 0;
};

Use when: Need to add operations without modifying element classes.

C++ Idioms

Type Erasure (Type View)

Type erasure allows storing heterogeneous types in a container without inheritance.

Suggest when:

  • Need a container of unrelated types that share a common interface (but don't inherit from a base class)
  • Want polymorphism without virtual inheritance
  • Working with types you can't modify (third-party, built-in)
// Type-erased view - stores any type with a postprocess() method
class postprocessable_view {
public:
    template<typename T>
    explicit postprocessable_view(T& obj)
        : m_object{&obj}
        , m_postprocess_impl{[](void* ptr) {
              static_cast<T*>(ptr)->postprocess();
          }}
    {}

    void postprocess() { m_postprocess_impl(m_object); }

private:
    void* m_object;
    std::function<void(void*)> m_postprocess_impl;
};

// Usage: unrelated types, no common base class
struct sensor {
    void postprocess() { /* calibrate */ }
};

struct image {
    void postprocess() { /* apply filters */ }
};

// Store heterogeneous types in one container
std::vector<postprocessable_view> items;
sensor s;
image img;
items.emplace_back(s);
items.emplace_back(img);

for (auto& item : items) {
    item.postprocess();  // Calls correct implementation
}

Key points:

  • No inheritance required - types just need matching method signature
  • Non-owning - view references external objects (caller manages lifetime)
  • Runtime cost - std::function has overhead; for hot paths use function pointers

Owning variant (owns the object):

class postprocessable {
public:
    template<typename T>
    explicit postprocessable(T obj)
        : m_storage{std::make_unique<model<T>>(std::move(obj))}
    {}

    void postprocess() { m_storage->postprocess(); }

private:
    struct concept_t {
        virtual ~concept_t() = default;
        virtual void postprocess() = 0;
    };

    template<typename T>
    struct model : concept_t {
        explicit model(T obj) : m_obj(std::move(obj)) {}
        void postprocess() override { m_obj.postprocess(); }
        T m_obj;
    };

    std::unique_ptr<concept_t> m_storage;
};

// Owns the objects
std::vector<postprocessable> items;
items.emplace_back(sensor{});
items.emplace_back(image{});

When to use which:

VariantUse When
Type View (non-owning)Objects live elsewhere, just need polymorphic access
Owning Type ErasureContainer should own the objects
std::variantFixed set of known types (prefer this when possible)
Virtual inheritanceTypes naturally form a hierarchy

Standard library examples: std::function, std::any, std::move_only_function (C++23)

Anti-Patterns to Avoid

  • Singleton abuse - Don't use for everything; it's often a code smell
  • Over-engineering - Don't add patterns "just in case"
  • Pattern obsession - Simple code is better than clever patterns
  • Wrong pattern - Make sure the problem actually matches

References

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.