Chain of Responsibility in C++

Behavioral Medium

In one sentence: Chain of Responsibility lets you pass a request down a line of handlers, where each one either handles it or forwards it to the next.

Real-world analogy

You carry a broken kettle back to the shop. There's one place to go — the returns counter — so you join that queue and explain the problem to the cashier. Small refunds are within her authority; yours isn't. "Let me get my supervisor."

The supervisor can approve up to a limit. Past that, it's the store manager. Past that, it's head office. Each person knows their own authority and exactly one thing beyond it: who stands above them. Nobody hands you a map of that ladder, and you never picked a rung — you spoke to one person and the shop worked out the rest internally.

Sometimes it simply ends. Head office rules the warranty expired eight months ago and nothing can be done. You go home still holding the kettle. Nothing malfunctioned: the request reached the end of the line without a taker, and that outcome is permitted by design.

Mapping the analogy to the pattern:

  • The kettle complaint you hand over → the request travelling along the chain.
  • The cashier, supervisor, store manager, head office → the ConcreteHandlers, each with its own authority limit and a link to exactly one successor above it.
  • "Let me get my supervisor" → the default behaviour GoF puts in Handler: if this one can't deal with it, forward it on unchanged.
  • The returns counter being the only place you go → the Client's single coupling. It hands the request to one handler and never learns who ends up answering.
  • Going home with the kettle → a request falling off the end of the chain unhandled — the pattern's accepted liability, not a bug.

Another angle: a doctor's referral. You book one appointment with your GP; they treat what's within their scope and refer upward when it isn't, and each specialist does the same. You never chose the cardiologist — the chain worked out who could answer.

The problem

You're processing support tickets. Some are trivial, some are emergencies, and different people handle different levels. The obvious first attempt is one big function:

  1. The if/else ladder grows forever. Every new rule ("security tickets skip the front desk") means editing the same function again, and one wrong branch breaks all the others.
  2. The sender must know everything. Whoever files the ticket ends up hardcoding who answers it — so you can't reorder the staff, add a night-shift bot, or reuse the engineer's logic elsewhere.

GoF's motivating example is context-sensitive help in a GUI. Click a Print button and you want help about that button; if nobody wrote button-specific help, you should fall back to help about the dialog, then about the application as a whole. The button that raises the help request has no idea which object will end up answering it — GoF calls this an implicit receiver.

The solution

Turn each decision-maker into its own small object, and link the objects together:

  1. Give every handler the same interface: look at the request, handle it, or pass it along.
  2. Each handler stores a pointer to the next handler in line — its successor.
  3. The client hands the request to the first handler and stops caring. Whoever is competent will pick it up.

Now the order is data, not code. Rearranging the chain is one line in main(), and a new handler is a new class that touches nothing else.

Note: GoF's implementation advice is to put the successor link and a default HandleRequest in the base Handler class, where the default simply forwards to the successor. Then a subclass that isn't interested in a request doesn't have to override anything at all — forwarding is what it inherits. GoF also points out you often don't need a new link: if your objects already sit in a part–whole tree, the parent reference is already the successor, and you can chain along it for free.

Structure

Structure of the Chain of Responsibility pattern Structure of the Chain of Responsibility pattern
Structure of the Chain of Responsibility pattern

Three participants, and a link that points from a handler back at its own type:

  • Handler — declares the interface for handling requests (HandleRequest()), and usually also implements the successor link and the default "pass it on" behaviour. In the example below this is Handler.
  • ConcreteHandler — handles the requests it's responsible for and can reach its successor. If it can handle the request, it does; otherwise it forwards. Here: FrontDesk, Engineer, Manager.
  • Client — kicks the request off by sending it to some handler on the chain — not necessarily the front of it. In the example, main() hands each ticket to desk.

C++ example

A support desk that escalates tickets by severity. Each handler owns the next one, so destroying the head destroys the whole chain.

#include <iostream>
#include <memory>
#include <string>

struct Ticket {
    std::string subject;
    int severity;   // 1 = trivial, 5 = the building is on fire
};

class Handler {
public:
    virtual ~Handler() = default;

    // Links the next handler and returns it, so calls can be chained.
    Handler* setNext(std::unique_ptr<Handler> next) {
        next_ = std::move(next);
        return next_.get();
    }

    void handle(const Ticket& t) {
        if (canHandle(t)) {
            std::cout << name() << " resolved: " << t.subject << "\n";
        } else if (next_) {
            std::cout << name() << " escalated: " << t.subject << "\n";
            next_->handle(t);
        } else {
            std::cout << name() << " is last in line, dropping: " << t.subject << "\n";
        }
    }

protected:
    virtual bool canHandle(const Ticket& t) const = 0;
    virtual std::string name() const = 0;

private:
    std::unique_ptr<Handler> next_;
};

class FrontDesk : public Handler {
protected:
    bool canHandle(const Ticket& t) const override { return t.severity <= 1; }
    std::string name() const override { return "FrontDesk"; }
};

class Engineer : public Handler {
protected:
    bool canHandle(const Ticket& t) const override { return t.severity <= 3; }
    std::string name() const override { return "Engineer"; }
};

class Manager : public Handler {
protected:
    bool canHandle(const Ticket& t) const override { return t.severity <= 4; }
    std::string name() const override { return "Manager"; }
};

int main() {
    auto desk = std::make_unique<FrontDesk>();
    desk->setNext(std::make_unique<Engineer>())
        ->setNext(std::make_unique<Manager>());

    Ticket tickets[] = {{"Password reset", 1},
                        {"Checkout returns 500", 3},
                        {"Data centre offline", 5}};

    for (const Ticket& t : tickets) {
        desk->handle(t);
        std::cout << "---\n";
    }
}
Output
FrontDesk resolved: Password reset
---
FrontDesk escalated: Checkout returns 500
Engineer resolved: Checkout returns 500
---
FrontDesk escalated: Data centre offline
Engineer escalated: Data centre offline
Manager is last in line, dropping: Data centre offline
---

main() only ever talks to desk — the escalation path is decided entirely inside the chain, and an unhandled request falls off the end instead of crashing.

When should you use it?

GoF's applicability list is three bullets. Use Chain of Responsibility when:

  • More than one object may handle a request, and you can't know which one in advance — the right handler should be worked out automatically at runtime.
  • You want to issue a request to one of several objects without naming the receiver — middleware stacks, validation pipelines, event filters.
  • The set of objects that can handle a request should be decided dynamically, not baked in at compile time.

Pros and cons

Pros (GoF's consequences)

  • Reduced coupling — the sender doesn't know which object will handle the request, and neither the sender nor the receiver holds an explicit reference to the other. A handler doesn't even need to know the chain's shape.
  • Simpler interconnections — instead of every object holding references to all candidate receivers, each keeps a single reference to its successor.
  • Flexibility in assigning responsibilities — you can add or change who handles what by rewiring the chain at run time, and combine that with subclassing to specialise individual handlers.

Cons

  • Receipt isn't guaranteed — this is GoF's own liability, not an implementation slip. Because there's no explicit receiver, a request can fall off the end of the chain and never be handled, and a misconfigured chain fails the same silent way.
  • Debugging is harder: you have to walk the chain to find who swallowed a request.
  • Long chains add call overhead and can make control flow hard to follow.

Tip: Always decide what happens when nobody handles the request. Logging it, throwing, or ending the chain with a catch-all "default handler" beats letting requests disappear quietly.

How to remember it

Memory hook: "Pass the bucket until someone drinks." The bucket goes down the line and each person either drinks or hands it on — and if nobody drinks, it just reaches the end still full. Contrast with Decorator, which uses the same linked shape but where every link always does its bit: a chain looks for one taker, a decorator stacks all of them.

  • Composite — the pairing GoF singles out: apply the two together and a component's parent becomes its successor, so the chain runs up the tree for free with no extra links.
  • Command — the request travelling down the chain is often a command object; GoF notes libraries that pass command-like "task" objects along a chain until something consumes them.
  • Decorator — same linked structure, but every link always runs, instead of one link claiming the request.