[2026] C++ Command Pattern: Complete Guide | Undo, Redo, Macros & Queues

[2026] C++ Command Pattern: Complete Guide | Undo, Redo, Macros & Queues

이 글의 핵심

Command pattern in C++: encapsulate requests as objects for undo/redo, macros, transactions, and async work—editor and banking-style examples with SEO keywords.

Behavioral patterns are covered in C++ behavioral patterns #20-1 and the overview #20-2.

What is Command Pattern? why you need it

Problem Scenario: Implementing Undo

Problem: To implement Undo/Redo in a text editor, you need to record all your actions and execute them in reverse order. It is difficult to record if the work is done only through function calls. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// Bad example: only function calls
void insertText(std::string& doc, const std::string& text) {
    doc += text;
// How to Undo?
}

Solution: Command Pattern encapsulates the request into an object. Each Command has execute() and undo() and is stored in the history stack. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Good example: Command object
// 타입 정의
class InsertCommand : public Command {
public:
    InsertCommand(std::string& doc, const std::string& text)
        : doc_(doc), text_(text), position_(doc.size()) {}
    
    void execute() override {
        doc_ += text_;
    }
    
    void undo() override {
        doc_.erase(position_, text_.size());
    }
    
private:
    std::string& doc_;
    std::string text_;
    size_t position_;
};

아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 실행 예제
flowchart TD
    invoker["Invoker (Editor)"]
    cmd[Command]
    insert[InsertCommand]
    delete[DeleteCommand]
    receiver["Receiver (Document)"]
    
    invoker -->|execute| cmd
    cmd <|-- insert
    cmd <|-- delete
    insert --> receiver
    delete --> receiver

index

  1. Basic structure
  2. Undo/Redo Implementation
  3. Macro system
  4. Transaction
  5. Frequently occurring problems and solutions
  6. Production Patterns
  7. Complete example: Text editor

1. basic structure

Minimum Command

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <memory>
class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};
class Light {
public:
    void on() { std::cout << "Light ON\n"; }
    void off() { std::cout << "Light OFF\n"; }
};
class LightOnCommand : public Command {
public:
    LightOnCommand(Light& light) : light_(light) {}
    
    void execute() override { light_.on(); }
    void undo() override { light_.off(); }
    
private:
    Light& light_;
};
class LightOffCommand : public Command {
public:
    LightOffCommand(Light& light) : light_(light) {}
    
    void execute() override { light_.off(); }
    void undo() override { light_.on(); }
    
private:
    Light& light_;
};
class RemoteControl {
public:
    void setCommand(std::unique_ptr<Command> cmd) {
        command_ = std::move(cmd);
    }
    
    void pressButton() {
        if (command_) {
            command_->execute();
        }
    }
    
    void pressUndo() {
        if (command_) {
            command_->undo();
        }
    }
    
private:
    std::unique_ptr<Command> command_;
};
int main() {
    Light light;
    RemoteControl remote;
    
    remote.setCommand(std::make_unique<LightOnCommand>(light));
    remote.pressButton();  // Light ON
    remote.pressUndo();    // Light OFF
}

2. Undo/Redo implementation

History Stack

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <memory>
#include <stack>
#include <string>
class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};
class Document {
public:
    void insert(const std::string& text) {
        content_ += text;
        std::cout << "Document: " << content_ << '\n';
    }
    
    void remove(size_t pos, size_t len) {
        content_.erase(pos, len);
        std::cout << "Document: " << content_ << '\n';
    }
    
    const std::string& getContent() const { return content_; }
    
private:
    std::string content_;
};
class InsertCommand : public Command {
public:
    InsertCommand(Document& doc, const std::string& text)
        : doc_(doc), text_(text), position_(doc.getContent().size()) {}
    
    void execute() override {
        doc_.insert(text_);
    }
    
    void undo() override {
        doc_.remove(position_, text_.size());
    }
    
private:
    Document& doc_;
    std::string text_;
    size_t position_;
};
class CommandManager {
public:
    void executeCommand(std::unique_ptr<Command> cmd) {
        cmd->execute();
        undoStack_.push(std::move(cmd));
        
// Clear Redo stack
        while (!redoStack_.empty()) {
            redoStack_.pop();
        }
    }
    
    void undo() {
        if (!undoStack_.empty()) {
            auto cmd = std::move(undoStack_.top());
            undoStack_.pop();
            
            cmd->undo();
            redoStack_.push(std::move(cmd));
        }
    }
    
    void redo() {
        if (!redoStack_.empty()) {
            auto cmd = std::move(redoStack_.top());
            redoStack_.pop();
            
            cmd->execute();
            undoStack_.push(std::move(cmd));
        }
    }
    
private:
    std::stack<std::unique_ptr<Command>> undoStack_;
    std::stack<std::unique_ptr<Command>> redoStack_;
};
int main() {
    Document doc;
    CommandManager manager;
    
    manager.executeCommand(std::make_unique<InsertCommand>(doc, "Hello "));
    manager.executeCommand(std::make_unique<InsertCommand>(doc, "World"));
    
    manager.undo();  // "Hello "
    manager.undo();  // ""
    manager.redo();  // "Hello "
    manager.redo();  // "Hello World"
}

3. macro system

Compound Command

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <memory>
#include <vector>
class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};
class MacroCommand : public Command {
public:
    void add(std::unique_ptr<Command> cmd) {
        commands_.push_back(std::move(cmd));
    }
    
    void execute() override {
        for (auto& cmd : commands_) {
            cmd->execute();
        }
    }
    
    void undo() override {
// undo in reverse order
        for (auto it = commands_.rbegin(); it != commands_.rend(); ++it) {
            (*it)->undo();
        }
    }
    
private:
    std::vector<std::unique_ptr<Command>> commands_;
};
class PrintCommand : public Command {
public:
    PrintCommand(const std::string& msg) : message_(msg) {}
    
    void execute() override {
        std::cout << message_ << '\n';
    }
    
    void undo() override {
        std::cout << "Undo: " << message_ << '\n';
    }
    
private:
    std::string message_;
};
int main() {
    auto macro = std::make_unique<MacroCommand>();
    macro->add(std::make_unique<PrintCommand>("Step 1"));
    macro->add(std::make_unique<PrintCommand>("Step 2"));
    macro->add(std::make_unique<PrintCommand>("Step 3"));
    
    macro->execute();
    // Step 1
    // Step 2
    // Step 3
    
    macro->undo();
    // Undo: Step 3
    // Undo: Step 2
    // Undo: Step 1
}

4. transaction

All-or-Nothing

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <memory>
#include <vector>
#include <stdexcept>
class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};
class Transaction {
public:
    void add(std::unique_ptr<Command> cmd) {
        commands_.push_back(std::move(cmd));
    }
    
    bool commit() {
        try {
            for (auto& cmd : commands_) {
                cmd->execute();
            }
            return true;
        } catch (const std::exception& e) {
            std::cerr << "Transaction failed: " << e.what() << '\n';
            rollback();
            return false;
        }
    }
    
    void rollback() {
        for (auto it = commands_.rbegin(); it != commands_.rend(); ++it) {
            try {
                (*it)->undo();
            } catch (...) {
// Rollback failure is ignored
            }
        }
    }
    
private:
    std::vector<std::unique_ptr<Command>> commands_;
};
class Account {
public:
    Account(double balance) : balance_(balance) {}
    
    void deposit(double amount) {
        balance_ += amount;
        std::cout << "Deposited $" << amount << ", Balance: $" << balance_ << '\n';
    }
    
    void withdraw(double amount) {
        if (balance_ < amount) {
            throw std::runtime_error("Insufficient funds");
        }
        balance_ -= amount;
        std::cout << "Withdrew $" << amount << ", Balance: $" << balance_ << '\n';
    }
    
private:
    double balance_;
};
class DepositCommand : public Command {
public:
    DepositCommand(Account& acc, double amount) : account_(acc), amount_(amount) {}
    
    void execute() override { account_.deposit(amount_); }
    void undo() override { account_.withdraw(amount_); }
    
private:
    Account& account_;
    double amount_;
};
class WithdrawCommand : public Command {
public:
    WithdrawCommand(Account& acc, double amount) : account_(acc), amount_(amount) {}
    
    void execute() override { account_.withdraw(amount_); }
    void undo() override { account_.deposit(amount_); }
    
private:
    Account& account_;
    double amount_;
};
int main() {
    Account acc(100.0);
    
    Transaction txn;
    txn.add(std::make_unique<WithdrawCommand>(acc, 50.0));
    txn.add(std::make_unique<DepositCommand>(acc, 30.0));
txn.add(std::make_unique<WithdrawCommand>(acc, 100.0));  // failure
    
    if (!txn.commit()) {
        std::cout << "Transaction rolled back\n";
    }
}

5. Frequently occurring problems and solutions

Problem 1: Receiver life cycle

Symptom: Dangling reference. Cause: Command refers to Receiver, but Receiver is destroyed first. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ Misuse: See
class Command {
Receiver& receiver_;  // Dangling possible
};
// ✅ Correct usage: shared_ptr
class Command {
    std::shared_ptr<Receiver> receiver_;
};

Problem 2: Undo impossible Command

Symptom: Cannot be restored when undoing. Cause: The state was not saved. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ Incorrect use: stateless
class DeleteCommand : public Command {
    void undo() override {
// How to restore deleted data?
    }
};
// ✅ Correct use: Save state
class DeleteCommand : public Command {
std::string deletedText_;  // save
    
    void execute() override {
        deletedText_ = doc_.getText();
        doc_.clear();
    }
    
    void undo() override {
        doc_.setText(deletedText_);
    }
};

6. production pattern

Pattern 1: History Restrictions

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class CommandManager {
public:
    CommandManager(size_t maxHistory = 100) : maxHistory_(maxHistory) {}
    
    void executeCommand(std::unique_ptr<Command> cmd) {
        cmd->execute();
        undoStack_.push(std::move(cmd));
        
// history limit
        if (undoStack_.size() > maxHistory_) {
            undoStack_.pop();
        }
        
        while (!redoStack_.empty()) {
            redoStack_.pop();
        }
    }
    
private:
    size_t maxHistory_;
    std::stack<std::unique_ptr<Command>> undoStack_;
    std::stack<std::unique_ptr<Command>> redoStack_;
};

Pattern 2: Asynchronous Command

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <future>
class AsyncCommand : public Command {
public:
    void execute() override {
        future_ = std::async(std::launch::async, [this]() {
// asynchronous operation
        });
    }
    
    void wait() {
        if (future_.valid()) {
            future_.wait();
        }
    }
    
private:
    std::future<void> future_;
};

7. Complete example: text editor

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <memory>
#include <stack>
#include <string>
class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual std::string describe() const = 0;
    virtual ~Command() = default;
};
class TextEditor {
public:
    void insert(size_t pos, const std::string& text) {
        content_.insert(pos, text);
    }
    
    void erase(size_t pos, size_t len) {
        content_.erase(pos, len);
    }
    
    std::string getText(size_t pos, size_t len) const {
        return content_.substr(pos, len);
    }
    
    const std::string& getContent() const { return content_; }
    
    void print() const {
        std::cout << "Content: \"" << content_ << "\"\n";
    }
    
private:
    std::string content_;
};
class InsertCommand : public Command {
public:
    InsertCommand(TextEditor& editor, size_t pos, const std::string& text)
        : editor_(editor), position_(pos), text_(text) {}
    
    void execute() override {
        editor_.insert(position_, text_);
    }
    
    void undo() override {
        editor_.erase(position_, text_.size());
    }
    
    std::string describe() const override {
        return "Insert \"" + text_ + "\" at " + std::to_string(position_);
    }
    
private:
    TextEditor& editor_;
    size_t position_;
    std::string text_;
};
class DeleteCommand : public Command {
public:
    DeleteCommand(TextEditor& editor, size_t pos, size_t len)
        : editor_(editor), position_(pos), length_(len) {}
    
    void execute() override {
        deletedText_ = editor_.getText(position_, length_);
        editor_.erase(position_, length_);
    }
    
    void undo() override {
        editor_.insert(position_, deletedText_);
    }
    
    std::string describe() const override {
        return "Delete " + std::to_string(length_) + " chars at " + std::to_string(position_);
    }
    
private:
    TextEditor& editor_;
    size_t position_;
    size_t length_;
    std::string deletedText_;
};
class EditorController {
public:
    EditorController(TextEditor& editor) : editor_(editor) {}
    
    void execute(std::unique_ptr<Command> cmd) {
        std::cout << "Executing: " << cmd->describe() << '\n';
        cmd->execute();
        editor_.print();
        
        undoStack_.push(std::move(cmd));
        
        while (!redoStack_.empty()) {
            redoStack_.pop();
        }
    }
    
    void undo() {
        if (undoStack_.empty()) {
            std::cout << "Nothing to undo\n";
            return;
        }
        
        auto cmd = std::move(undoStack_.top());
        undoStack_.pop();
        
        std::cout << "Undoing: " << cmd->describe() << '\n';
        cmd->undo();
        editor_.print();
        
        redoStack_.push(std::move(cmd));
    }
    
    void redo() {
        if (redoStack_.empty()) {
            std::cout << "Nothing to redo\n";
            return;
        }
        
        auto cmd = std::move(redoStack_.top());
        redoStack_.pop();
        
        std::cout << "Redoing: " << cmd->describe() << '\n';
        cmd->execute();
        editor_.print();
        
        undoStack_.push(std::move(cmd));
    }
    
private:
    TextEditor& editor_;
    std::stack<std::unique_ptr<Command>> undoStack_;
    std::stack<std::unique_ptr<Command>> redoStack_;
};
int main() {
    TextEditor editor;
    EditorController controller(editor);
    
    controller.execute(std::make_unique<InsertCommand>(editor, 0, "Hello"));
    controller.execute(std::make_unique<InsertCommand>(editor, 5, " World"));
    controller.execute(std::make_unique<DeleteCommand>(editor, 5, 6));
    
    controller.undo();
    controller.undo();
    controller.redo();
}

organize

conceptDescription
Command PatternEncapsulate request into object
PurposeUndo/Redo, Macro, Transaction, Queue
StructureCommand, Invoker, Receiver
AdvantagesRequest history, cancelable, combinable
Disadvantagesclass increase, memory usage
Use CaseEditor, GUI, Transactions, Task Queue
Command Pattern is a powerful pattern that objectifies requests to implement Undo/Redo and macros.

FAQ

Q1: When do I use Command Pattern?

A: Used when Undo/Redo, Macro, Transaction, and Task Queue are required.

Q2: What is the difference from Memento Pattern?

A: Command focuses on action history, Memento focuses on state snapshots.

Q3: What is the memory usage?

A: Memory increases as the history stack grows. Limit history.

Q4: What is an asynchronous Command?

A: Asynchronous execution is possible with std::async or std::thread.

Q5: What is the Receiver life cycle?

A: Manage it with shared_ptr, or ensure that the Command is destroyed before the Receiver.

Q6: What are Command Pattern learning resources?

A:

  • “Design Patterns” by Gang of Four
  • “Head First Design Patterns” by Freeman & Freeman
  • Refactoring Guru: Command Pattern One line summary: Command Pattern allows you to objectify requests and implement Undo/Redo. Next, it would be a good idea to read State Pattern.

Good article to read together (internal link)

Here’s another article related to this topic.

Practical tips

These are tips that can be applied right away in practice.

Debugging tips

  • If you run into a problem, check the compiler warnings first.
  • Reproduce the problem with a simple test case

Performance Tips

  • Don’t optimize without profiling
  • Set measurable indicators first

Code review tips

  • Check in advance for areas that are frequently pointed out in code reviews.
  • Follow your team’s coding conventions

Practical checklist

This is what you need to check when applying this concept in practice.

Before writing code

  • Is this technique the best way to solve the current problem?
  • Can team members understand and maintain this code?
  • Does it meet the performance requirements?

Writing code

  • Have you resolved all compiler warnings?
  • Have you considered edge cases?
  • Is error handling appropriate?

When reviewing code

  • Is the intent of the code clear?
  • Are there enough test cases?
  • Is it documented? Use this checklist to reduce mistakes and improve code quality.

Keywords covered in this article (related search terms)

This article will be helpful if you search for C++, command, pattern, undo, redo, macro, queue, etc.

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3