Command in C++

Behavioral Medium

In one sentence: Command turns an action into an object, so the action can be stored, passed around, replayed — and, best of all, undone.

Real-world analogy

You order the soup. The waiter doesn't head for the stove — he writes it on a slip: soup, table 4, no cream. Your wish has just become a physical object, and that changes everything about what can be done with it.

The slip goes on the kitchen rail behind five others, waiting its turn. Whichever cook is free lifts it down and makes the dish; the waiter never learns how soup is made, and the cook never learns who asked. Change your mind and the slip comes off the rail and gets crossed out — the same piece of paper that ordered the dish is what cancels it.

At the end of service the spike holds every slip of the night, in the order they were fired. That stack is a record: what was made, in what sequence, and what to re-fire if a table sends a plate back.

Mapping the analogy to the pattern:

  • The order slip → the ConcreteCommand: an action reified as an object you can hold, hand over, and file.
  • Writing "soup, no cream" on it → parameterising the command at creation with its receiver and its arguments.
  • The waiter → the Invoker: he issues the request and keeps the paperwork, and never learns what carrying it out involves.
  • The rail of pending slips → a command queue, with the cook who takes one down as the Receiver that does the real work — specified now, executed later, by someone else.
  • The spike of finished slips → the history list: crossing one out is undo(), re-firing it is redo.

Another angle: a cheque. It's an instruction to pay, written down — so you can post it, post-date it, hand it to someone who isn't the payer, keep the stub as a record, and stop it before it clears. The bank is what actually moves the money.

The problem

You're building a text editor. Every button and shortcut modifies the document. If each one calls document.append(...) directly, you hit a wall the moment someone asks for Ctrl+Z:

  1. The action is gone the instant it runs. A function call leaves nothing behind, so there is nothing to reverse. To undo, you'd need to remember what changed and what it was before — and that information lives nowhere.
  2. The UI ends up owning the logic. Buttons, menu items, and macros each grow their own copy of the same edit code, and none of it can be reused by a script or a keyboard shortcut.

The solution

Give every action a class with two methods: execute() and undo().

  1. Each command captures what it needs — the receiver that will do the real work, plus the arguments — when it's created.
  2. execute() performs the change by calling operations on the receiver and, if needed, records enough of the old state to reverse it.
  3. An invoker runs commands and pushes them onto a history stack. Undo means popping the top command and calling its undo().

Undo stops being a special feature bolted on at the end. It's just the other half of every command. And because commands are ordinary objects, one command can hold a list of others: GoF calls that a MacroCommand, and it's how "record a macro" and "scripting" fall out of the pattern for free.

Note: GoF describes commands as a spectrum of intelligence. At one end a command is nothing but a binding between a receiver and one of its methods; at the other it does the whole job itself with no receiver at all. The heavy end is right when no suitable receiver exists — Shout below is close to it — but the thin end keeps commands reusable. GoF also warns about a subtle trap: if a command's state changes each time it runs (a Delete that captures a different selection every time), you must push a copy onto the history list, not the live object. Commands used that way are acting as prototypes.

Structure

Structure of the Command pattern Structure of the Command pattern
Structure of the Command pattern

Five participants, and the split between the last two is the whole point:

  • Command — declares the interface for performing an operation, classically just Execute(). Here: the Command base class (with undo() added, which GoF calls Unexecute).
  • ConcreteCommand — binds a receiver to an action and implements Execute() by invoking operations on that receiver. Here: AppendText and Shout.
  • Receiver — knows how to actually carry out the work. GoF notes any class can serve as a receiver. Here: Document.
  • Invoker — holds the command and asks it to run; it never learns what the command does. Here: Editor, which also keeps the history list.
  • Client — creates the concrete command and hands it its receiver. Here: main().

C++ example

A tiny editor with a real undo stack. The invoker (Editor) owns the history, so commands stay alive as long as they can still be undone.

#include <cctype>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Document {
public:
    void append(const std::string& s) { text_ += s; }
    void chop(std::size_t n)          { text_.erase(text_.size() - n); }
    void setText(std::string t)       { text_ = std::move(t); }
    const std::string& text() const   { return text_; }
private:
    std::string text_;
};

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

class AppendText : public Command {
public:
    AppendText(Document& doc, std::string s) : doc_(doc), s_(std::move(s)) {}
    void execute() override { doc_.append(s_); }
    void undo() override    { doc_.chop(s_.size()); }
private:
    Document& doc_;
    std::string s_;
};

class Shout : public Command {   // uppercase the whole document
public:
    explicit Shout(Document& doc) : doc_(doc) {}
    void execute() override {
        before_ = doc_.text();               // remember, so we can put it back
        std::string loud = before_;
        for (char& c : loud) c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
        doc_.setText(std::move(loud));
    }
    void undo() override { doc_.setText(before_); }
private:
    Document& doc_;
    std::string before_;
};

class Editor {
public:
    void run(std::unique_ptr<Command> cmd) {
        cmd->execute();
        history_.push_back(std::move(cmd));
    }
    void undo() {
        if (history_.empty()) return;
        history_.back()->undo();
        history_.pop_back();
    }
private:
    std::vector<std::unique_ptr<Command>> history_;
};

int main() {
    Document doc;
    Editor editor;
    editor.run(std::make_unique<AppendText>(doc, "hello"));
    editor.run(std::make_unique<AppendText>(doc, " world"));
    editor.run(std::make_unique<Shout>(doc));
    std::cout << "after 3 commands: [" << doc.text() << "]\n";
    for (int i = 1; i <= 3; ++i) {
        editor.undo();
        std::cout << "after " << i << " undo(s):  [" << doc.text() << "]\n";
    }
}
Output
after 3 commands: [HELLO WORLD]
after 1 undo(s):  [hello world]
after 2 undo(s):  [hello]
after 3 undo(s):  []

The editor rewinds three different kinds of edit without knowing what any of them did — each command knew how to reverse itself.

When should you use it?

GoF's applicability list, in plain speech. Use Command when you want to:

  • Parameterise an object by the action it should perform. A button shouldn't contain edit logic; it should hold a command. GoF calls commands the object-oriented replacement for callbacks.
  • Specify, queue, and execute requests at different times. A command has a lifetime independent of the request that created it — it can sit in a queue, or even be shipped to another process and fulfilled there.
  • Support undo. Execute() stores whatever it needs to reverse itself, an Unexecute() puts it back, and a history list traversed backwards and forwards gives you unlimited undo and redo.
  • Log changes so they can be reapplied after a crash. Add load/store to the command interface, keep a persistent log, and recovery is replaying it.
  • Build high-level operations out of primitive ones — transactions. Every transaction has the same interface, so you invoke them all the same way and new ones cost nothing to add.

Pros and cons

Pros (GoF's consequences)

  • Decouples the invoker from the performer — the object that issues a request only needs to know how to issue it, not how it's carried out. That's why a menu item and a toolbar button can share one command instance.
  • Commands are first-class objects — they can be stored, passed, extended, and manipulated like any other object, which is what makes queueing, logging, and history lists possible at all.
  • Commands compose — you can assemble them into a composite command (GoF's MacroCommand), which is just an instance of the Composite pattern.
  • New commands are easy to add — a new class, and no existing class changes.

Cons

  • A lot of small classes for what used to be one-line calls.
  • Every command must correctly capture the state needed to reverse itself — easy to get subtly wrong. GoF warns that errors accumulate across repeated execute/unexecute/re-execute cycles until the application's state has quietly drifted.
  • The history stack keeps objects (and their captured data) alive, which costs memory.

Note: Not every action can be undone by remembering a few fields. When the change is large or messy, store a snapshot instead — GoF recommends exactly this, because a Memento gives the command what it needs to restore another object without exposing that object's internals.

How to remember it

Memory hook: "A ticket you can keep." The action is written down as an object, so it can be queued, filed, replayed, or torn up. Contrast with Strategy: a command is a thing to DO, a strategy is a way to DO it. If you'd put it on an undo stack, it's a command; if you'd swap it to change an algorithm, it's a strategy.

  • Composite — GoF's stated pairing: a composite is what you use to implement MacroCommands, one command holding a sequence of others. (Undoing a macro means unexecuting its subcommands in reverse order.)
  • Memento — the classic undo partner: the memento holds the state the command needs to reverse its effect.
  • Prototype — a command whose state changes on each run must be copied before it goes on the history list, and GoF says such commands act as prototypes.
  • Chain of Responsibility — commands are often the requests passed down a chain of handlers until one interprets them.