BehavioralC++verifiedVerified
Memento Pattern in C++
Captures and externalizes an object's internal state without violating encapsulation, allowing the object to be restored to that state later.
How to Implement the Memento Pattern in C++
1Step 1: Define the Memento (opaque snapshot)
class Memento {
friend class TextEditor;
std::string state_;
explicit Memento(std::string state) : state_(std::move(state)) {}
};2Step 2: Originator that creates and restores from mementos
class TextEditor {
std::string text_;
public:
void type(const std::string& s) { text_ += s; }
Memento save() const { return Memento(text_); }
void restore(const Memento& m) { text_ = m.state_; }
const std::string& getText() const { return text_; }
};3Step 3: Caretaker manages the memento history
class History {
std::vector<Memento> snapshots_;
public:
void push(Memento m) { snapshots_.push_back(std::move(m)); }
Memento pop() {
auto m = std::move(snapshots_.back());
snapshots_.pop_back();
return m;
}
bool empty() const { return snapshots_.empty(); }
};
int main() {
TextEditor editor;
History history;
editor.type("Hello");
history.push(editor.save());
editor.type(" World");
history.push(editor.save());
editor.type("!!!");
std::cout << "Current: " << editor.getText() << "\n";
editor.restore(history.pop());
std::cout << "Undo 1: " << editor.getText() << "\n";
editor.restore(history.pop());
std::cout << "Undo 2: " << editor.getText() << "\n";
}#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <format>
#include <chrono>
#include <deque>
#include <stdexcept>
// [step] Typed memento with metadata
template <typename State>
class Memento {
template <typename> friend class MementoCaretaker;
State state_;
std::string description_;
std::chrono::system_clock::time_point createdAt_;
public:
Memento(State state, std::string desc)
: state_(std::move(state)),
description_(std::move(desc)),
createdAt_(std::chrono::system_clock::now()) {}
const std::string& description() const { return description_; }
const auto& createdAt() const { return createdAt_; }
const State& state() const { return state_; }
};
// [step] Caretaker with configurable max snapshots and named checkpoints
template <typename State>
class MementoCaretaker {
std::deque<Memento<State>> undoStack_;
std::deque<Memento<State>> redoStack_;
std::map<std::string, Memento<State>> checkpoints_;
size_t maxSize_;
public:
explicit MementoCaretaker(size_t maxSize = 50) : maxSize_(maxSize) {}
void save(Memento<State> memento) {
undoStack_.push_back(std::move(memento));
redoStack_.clear();
if (undoStack_.size() > maxSize_) undoStack_.pop_front();
}
void checkpoint(const std::string& name, Memento<State> memento) {
checkpoints_.emplace(name, std::move(memento));
}
const Memento<State>& undo() {
if (undoStack_.empty()) throw std::runtime_error("Nothing to undo");
redoStack_.push_back(std::move(undoStack_.back()));
undoStack_.pop_back();
return redoStack_.back();
}
const Memento<State>& redo() {
if (redoStack_.empty()) throw std::runtime_error("Nothing to redo");
undoStack_.push_back(std::move(redoStack_.back()));
redoStack_.pop_back();
return undoStack_.back();
}
const Memento<State>& getCheckpoint(const std::string& name) const {
auto it = checkpoints_.find(name);
if (it == checkpoints_.end())
throw std::runtime_error(std::format("Checkpoint '{}' not found", name));
return it->second;
}
size_t undoCount() const { return undoStack_.size(); }
size_t redoCount() const { return redoStack_.size(); }
};
// [step] Originator: a spreadsheet cell model
struct SpreadsheetState {
std::map<std::string, std::string> cells;
std::string activeCell;
};
class Spreadsheet {
SpreadsheetState state_;
public:
void setCell(const std::string& ref, const std::string& value) {
state_.cells[ref] = value;
state_.activeCell = ref;
}
std::string getCell(const std::string& ref) const {
auto it = state_.cells.find(ref);
return it != state_.cells.end() ? it->second : "";
}
Memento<SpreadsheetState> save(const std::string& desc) const {
return Memento<SpreadsheetState>(state_, desc);
}
void restore(const Memento<SpreadsheetState>& memento) {
state_ = memento.state();
}
size_t cellCount() const { return state_.cells.size(); }
};
// [step] Demonstrate undo/redo and checkpoints
int main() {
Spreadsheet sheet;
MementoCaretaker<SpreadsheetState> caretaker(20);
sheet.setCell("A1", "Revenue");
caretaker.save(sheet.save("Set A1"));
sheet.setCell("B1", "1000");
caretaker.save(sheet.save("Set B1"));
// Named checkpoint
caretaker.checkpoint("baseline", sheet.save("Baseline snapshot"));
sheet.setCell("C1", "Profit");
caretaker.save(sheet.save("Set C1"));
std::cout << std::format("Cells: {} | Undo={} Redo={}\n",
sheet.cellCount(), caretaker.undoCount(), caretaker.redoCount());
// Undo
auto& m1 = caretaker.undo();
sheet.restore(m1);
std::cout << std::format("After undo ({}): cells={}\n",
m1.description(), sheet.cellCount());
// Redo
auto& m2 = caretaker.redo();
sheet.restore(m2);
std::cout << std::format("After redo ({}): cells={}\n",
m2.description(), sheet.cellCount());
// Restore checkpoint
auto& baseline = caretaker.getCheckpoint("baseline");
sheet.restore(baseline);
std::cout << std::format("Restored '{}': cells={}\n",
baseline.description(), sheet.cellCount());
}Memento Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Memento Pattern in the Real World
“Think of a video game save point. When you save, the game (originator) packages your character's stats, inventory, and position into a save file (memento). The save system (caretaker) stores these files without understanding their contents. When you die and reload, the save file is handed back to the game, which restores everything exactly as it was.”