Hello! Welcome back to our series on C++ design patterns.
In our last lesson, we delved into the Observer pattern, a mechanism for creating one-to-many dependencies and broadcasting events. We saw how a Subject can notify multiple Observers of state changes without being tightly coupled to them, and we paid special attention to the critical C++ challenge of managing observer lifetimes using RAII.
Today, we shift from one-to-many broadcasts to encapsulating a single, discrete action. We will learn to implement the Command pattern to encapsulate a request as an object, enabling queuing and undoable operations.
The Observer pattern answers "what just happened?", while the Command pattern answers "what should be done?". This concept of turning an action into an object is a cornerstone of flexible software design. It's the key to implementing features like editor undo/redo, transactional databases, and, most importantly for our course, the task queues that are fundamental to the thread pool you'll be building.
1. The Core Idea: Turning a Method Call into an Object
The Gang of Four defines the Command pattern's intent as: "Encapsulate a request as an object". A more intuitive way to think about this comes from the book Game Programming Patterns, which describes a command as a reified method call. "Reify" means to make something abstract (like an action or a request) into something concrete (an object).
By wrapping a request in an object, you gain incredible power. You can:
- Defer execution: Store the command object and execute it later.
- Queue requests: Put command objects into a list to be executed sequentially.
- Log requests: Save a history of commands, which can be used for replay or recovery.
- Undo/Redo: Store extra state in the command object so it can reverse its own action.
The pattern formalizes the relationships between the different actors involved in a request.

Let's break down these roles:
- Command: An interface with an
execute()method (and often anundo()method). - ConcreteCommand: A specific implementation that binds together a Receiver and an action.
- Receiver: The object that knows how to perform the actual work (e.g., a game character, a text editor document).
- Invoker: The object that asks the command to carry out the request. It doesn't know what the command does, only that it can be executed.
- Client: The part of the application that creates the commands and configures the invoker.
2. Implementation: From Simple Actions to Undoable Operations
Let's see how to build this in C++. We'll start with a simple game character that can be moved around and then add the ability to queue commands and undo them.
Design Patterns - Command Pattern Explanation and Implementation in C++
To see a practical, step-by-step implementation, we'll watch a video from Mike Shah's design patterns series. He builds up the pattern from a basic abstract class to a working example with queuing and undo functionality, which perfectly matches our learning outcome.
Watch from the beginning to 09:02. This will introduce the Command pattern's intent, the game controller analogy, and the initial setup of the abstract Command class.
As you just saw, the foundation is an abstract base class that defines the common interface for all commands.
class Command {
public:
virtual ~Command() {}
virtual void execute() = 0;
virtual void undo() = 0; // We'll add this for undo support
};
A concrete command inherits from this and implements the execute method. The key is that this command object can now be passed around, stored, and invoked by any part of the system that holds a Command*.
Queuing and Deferring Execution
One of the main benefits of this pattern is the ability to decouple the time a request is made from the time it is executed. We can achieve this by storing command objects in a container, like a std::vector, and processing them later.
Design Patterns - Command Pattern Explanation and Implementation in C++
Let's continue with the same video. The next section demonstrates exactly how to defer execution by placing commands into a queue.
Watch from 11:13 to 24:09. Pay close attention to two key moments: The implementation of a concrete Move command that operates on a Character object (the Receiver). The use of a std::vector<Command*> as a command queue, where commands are added first and executed later in a loop.
Notice the use of new Move(). The command objects are allocated on the heap. This is typical because the invoker (in this case, the main function with its vector) needs to manage the lifetime of commands that are created in a different scope. This immediately brings memory management into focus, a topic we'll revisit often.
Implementing Undo
The ability to support undo is a classic use case for the Command pattern. To make a command undoable, the object itself must store enough state to reverse its execute() operation.
Undo and Redo · Design Patterns Revisited
The 'Game Programming Patterns' article provides a very clear explanation of how to add undo functionality. It shows how the MoveUnitCommand must be extended to remember the unit's previous position.
Read the section 'Undo and Redo'. Focus on how the MoveUnitCommand class is modified to include xBefore_ and yBefore_ members and how the undo() method uses this stored state.
Now, let's see this implemented in the running example from the video.
Design Patterns - Command Pattern Explanation and Implementation in C++
The final part of Mike Shah's implementation video shows how to complete the undo method and demonstrate the reversal of actions.
Watch from 24:28 to 32:04. Observe how the Move command stores the random x and y values it generated during execute() so that undo() can apply the negative of those values. The video also correctly points out that a std::stack is often a more natural data structure for an undo history.
3. A More Scalable Design: Separating Command, Invoker, and Receiver
The implementation we just saw is great, but the Move command had a lot of logic inside it. A more robust and common approach is to make the command object a lightweight object that simply delegates the call to a Receiver. This better follows the Single Responsibility Principle.
- ConcreteCommand: Knows which method to call on the Receiver and with what arguments.
- Receiver: Contains the actual business logic.
This decoupling is powerful. The same Receiver (e.g., a TextDocument) can have its actions (cut, paste) invoked by different commands, perhaps triggered by a menu item (PasteMenuCommand) or a keyboard shortcut (CtrlVCommand).
The Command Pattern - Programming Design Patterns - Ep 14 - C++ Coding - Must Know
Another excellent video, by Code, Tech, and Tutorials, focuses on this separation of concerns. It clearly distinguishes between simple commands (with embedded logic) and complex commands that delegate to a Receiver and are managed by an Invoker.
Watch from 08:00 to 17:34 and then from 21:51 to 28:05. Focus on three distinct classes being created: The Receiver, which contains the actual business logic (processA, processB). The ComplexCommand, which holds a pointer to the Receiver and whose execute() method just calls a method on the receiver. The Invoker, which holds and executes commands without knowing anything about the receiver or the command's internal details.
Test your understanding!
You are designing a simple text editor. You have a TextEditor class (the Receiver) with methods like insertText(pos, str), deleteText(pos, len), and save().
You need to implement commands for two actions:
- Pasting text from the clipboard.
- Saving the file.
Sketch the PasteCommand and SaveCommand concrete command classes. How would they use the TextEditor as a receiver? What state, if any, would PasteCommand need to store to support an undo() operation?
Show answer
Here's a possible design.
The Receiver:
class TextEditor {
public:
void insertText(size_t position, const std::string& text) { /* ... */ }
void deleteText(size_t position, size_t length) { /* ... */ }
void save() { /* ... */ }
// ... other methods
};
The Command Interface:
class ICommand {
public:
virtual ~ICommand() = default;
virtual void execute() = 0;
virtual void undo() = 0;
};
The Concrete Commands:SaveCommand is straightforward and doesn't support undo. Its job is to just call save() on the editor.
class SaveCommand : public ICommand {
private:
TextEditor* editor_; // Pointer to the receiver
public:
SaveCommand(TextEditor* editor) : editor_(editor) {}
void execute() override {
editor_->save();
}
void undo() override {
// Saving is not typically undoable in this context.
// We could log a message or do nothing.
}
};
PasteCommand is more complex. To support undo, it must remember the position and length of the text it inserted. The undo() operation would then be to delete that same text.
#include <string> // for std::string
// Assume a function to get clipboard content exists
std::string getClipboardContent();
class PasteCommand : public ICommand {
private:
TextEditor* editor_;
std::string textToPaste_;
size_t pastePosition_;
public:
PasteCommand(TextEditor* editor, size_t position)
: editor_(editor), pastePosition_(position) {}
void execute() override {
// Get content from clipboard *at execution time*
textToPaste_ = getClipboardContent();
editor_->insertText(pastePosition_, textToPaste_);
}
void undo() override {
// Use the stored state to reverse the operation
if (!textToPaste_.empty()) {
editor_->deleteText(pastePosition_, textToPaste_.length());
}
}
};
This design clearly separates the editor's logic from the command objects that trigger it.
4. Modern C++ & The "Under the Hood" View: std::function
The classic Command pattern is powerful but can feel heavyweight, requiring a new class for every action. As someone interested in C++ internals, you'll appreciate that the Command pattern is essentially an object-oriented way of emulating closures or first-class functions in a language that historically lacked them.
Modern C++ gives us std::function, a polymorphic function wrapper. It can store any "callable" thing (a lambda, a function pointer, a functor object) in a type-erased way. This allows for a more lightweight implementation of the Command pattern. Instead of a std::vector<ICommand*>, our task queue could be a std::vector<std::function<void()>>.
This is exactly how many modern task systems, including thread pools, are designed.
To understand this modern approach and its internal mechanics, let's read an article that dives deep into std::function. This will bridge the gap between classic design patterns and the tools you'll use in contemporary C++ systems.
This is a key article for understanding modern C++ internals. Please read the following sections carefully: 'std::function 101': Understand how it uses type erasure to store different callables. 'When to use std::function?': Pay close attention to the Producer-consumer task queue example. This is a direct preview of our thread pool project. 'What’s the overhead of std::function?': This is the core 'under the hood' part. It discusses the costs of indirect calls and potential dynamic allocations. 'Implementing a simplified std::function': This shows how type erasure is implemented using a virtual interface, which is very similar to the mechanism behind the classic Command pattern, but at the function level.
The key takeaways from that article are crucial for a systems developer:
std::functionprovides flexibility through type erasure, but this is not a zero-cost abstraction.- Invoking a
std::functiontypically involves an indirect function call (similar to a virtual method call), which can hinder compiler optimizations like inlining. std::functionmay perform a heap allocation if the callable it stores (e.g., a lambda with a large capture) is too big to fit in its internal buffer (an optimization known as Small Buffer Optimization or SBO).
Choosing between the classic polymorphic Command class and a std::function-based approach is a trade-off. The class-based approach is more explicit and might be better when commands have complex state or require both execute and undo. The std::function approach is often more concise and flexible for simple, one-shot tasks like those in a task queue.
Conclusion
You have now learned how to encapsulate a request into an object, giving you the power to queue, defer, and even reverse actions. This is a fundamental technique for building decoupled and flexible systems.
Key Takeaways:
- Intent: To turn a request into a stand-alone object that contains all information about the request.
- Structure: The pattern decouples the
Invoker(who triggers the request) from theReceiver(who performs the action) via aCommandobject. - Core Benefits: Enables queuing of requests, deferred execution, and support for undoable operations by having the command store the necessary state to reverse its action.
- Classic vs. Modern: The pattern can be implemented with a polymorphic class hierarchy or, more concisely in modern C++, with
std::functionand lambdas. - Performance Internals: Both approaches rely on a form of type erasure and indirect calls (virtual dispatch or function pointer invocation), which have performance implications compared to a direct function call.
Next Lesson Preview:
Now that we know how to encapsulate a unit of work (a Command), our next logical step is to think about executing these commands concurrently. The next module, "Concurrency and Architectural Patterns," will begin by teaching you how to implement the Monitor Object pattern. This pattern is all about creating a thread-safe class by bundling data with the synchronization logic (like mutexes) needed to protect it. This is the first essential building block for constructing the thread-safe task queue that will be at the heart of our thread pool project.