Memento Pattern
- Description: Capture an object's internal state into an opaque token so it can be restored later, without exposing internals to outside code.
- My Notion Note ID: K2C-2-17
- Created: 2026-05-22
- Updated: 2026-05-22
- License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io
Table of Contents
- 1. Intent
- 2. Structure
- 3. C++ Example
- 4. When to Use / When Not To
- 5. Variants and Pitfalls
- 6. Related Patterns
- 7. References
1. Intent
- Save + restore an object's state without breaking encapsulation.
- Token (Memento) is opaque to its keeper (Caretaker); only the originator can read it.
- Three responsibilities split across three roles — that's the whole pattern.
Typical uses:
- Undo/redo — push a Memento before each mutation; pop to restore.
- Checkpoint / rollback — long computation, save points.
- Snapshot for serialization — save game state, document state.
- Transactional commits — speculatively mutate, restore on abort.
2. Structure
Roles:
- Originator — the object whose state is being saved/restored. Creates Mementos (
save()) and accepts them back (restore(m)). - Memento — opaque value type holding the snapshot. Originator has full access; everyone else has none.
- Caretaker — keeps Mementos but never inspects them. Pushes/pops them on undo, holds a list.
Originator.save() ──► Memento ──► Caretaker (stores)
Originator.restore(Memento) ◄── Caretaker (gives back)
The encapsulation rule is the key constraint: in C++, friend declaration grants the Originator privileged access to Memento internals; the Caretaker only manipulates Mementos through their public (opaque) interface — typically just constructor, destructor, copy/move.
3. C++ Example
3.1 Friend-based encapsulation (canonical C++ form)
#include <vector>
#include <memory>
#include <string>
#include <iostream>
class Editor;
class EditorMemento {
std::string text_;
size_t cursor_;
EditorMemento(std::string t, size_t c)
: text_(std::move(t)), cursor_(c) {}
friend class Editor;
public:
EditorMemento(const EditorMemento&) = default;
EditorMemento(EditorMemento&&) = default;
EditorMemento& operator=(const EditorMemento&) = default;
EditorMemento& operator=(EditorMemento&&) = default;
~EditorMemento() = default;
};
class Editor {
std::string text_;
size_t cursor_ = 0;
public:
void type(const std::string& s) { text_ += s; cursor_ += s.size(); }
void move_cursor(size_t pos) { cursor_ = pos; }
const std::string& text() const { return text_; }
size_t cursor() const { return cursor_; }
EditorMemento save() const {
return EditorMemento{text_, cursor_};
}
void restore(const EditorMemento& m) {
text_ = m.text_;
cursor_ = m.cursor_;
}
};
class History {
std::vector<EditorMemento> stack_;
public:
void push(EditorMemento m) { stack_.push_back(std::move(m)); }
EditorMemento pop() {
auto m = std::move(stack_.back());
stack_.pop_back();
return m;
}
bool empty() const { return stack_.empty(); }
};
int main() {
Editor e;
History h;
e.type("Hello");
h.push(e.save()); // checkpoint 1
e.type(", World");
h.push(e.save()); // checkpoint 2
e.type(", and others");
std::cout << e.text() << "\n"; // Hello, World, and others
e.restore(h.pop()); // back to checkpoint 2
std::cout << e.text() << "\n"; // Hello, World
e.restore(h.pop()); // back to checkpoint 1
std::cout << e.text() << "\n"; // Hello
}
Const correctness:
save() const— taking a snapshot must not mutate the Originator.- Memento's members are non-
const(it's a value type that gets copied/moved), but they areprivate. Public-facing immutability is enforced by the absence of mutators. restore(const EditorMemento&)— Memento not modified by restore; passed by const reference.
3.2 Wide-interface Memento (pragmatic shortcut)
When encapsulation strictness is overkill (small project, no external Caretakers), expose a public POD:
struct Snapshot {
std::string text;
size_t cursor;
};
class Editor2 {
std::string text_;
size_t cursor_ = 0;
public:
Snapshot save() const { return {text_, cursor_}; }
void restore(const Snapshot& s) { text_ = s.text; cursor_ = s.cursor; }
};
Trade-off: any code can read and mutate the snapshot. Lose Memento's encapsulation guarantee — gain simplicity.
3.3 Heavy-state Memento — incremental snapshots
Full snapshots cost memory. For large state, store deltas (command-pattern hybrid) or use copy-on-write structures.
4. When to Use / When Not To
Use when:
- Need to restore an object's earlier state (undo, rollback, checkpoint).
- Direct exposure of internals would violate encapsulation but you still need snapshots.
- Want save/restore independent of which fields exist today — Memento type evolves with Originator.
Don't use when:
- State is cheap to recompute → recompute instead.
- The whole object is small, immutable, or already a value type → just copy it.
- Snapshots are huge and frequent → use Command pattern (record actions, replay inverses) instead.
- State changes are append-only → event sourcing is a better fit.
5. Variants and Pitfalls
Variants:
- Wide-interface Memento — public fields, no friend. Loses encapsulation; common in practice.
- Narrow-interface Memento — friend-based; canonical GoF form. § 3.1 above.
- Incremental Memento — delta snapshots; smaller but harder to restore (must replay forward from a base).
- Serializable Memento — Memento doubles as a save-file format. Add versioning carefully.
- Copy-on-write state — Originator shares immutable state via
shared_ptr; Memento = the shared pointer. Cheap snapshots.
Pitfalls:
- Unbounded history. Mementos accumulate; bound the stack or compress.
- Deep vs shallow copy. If Memento holds pointers/references into Originator state, mutations to that state leak into the saved snapshot. Deep-copy or use immutable shared state.
- Memento outliving Originator. Memento may be safe to outlive (it's a value), but if it holds raw pointers to the Originator, restore on a different instance is UB.
- Friend coupling. Memento becomes coupled to Originator's internals — refactors hit both. Acceptable price for encapsulation.
- Non-deterministic Originator (e.g., depends on external resources) — restore puts the object in a logically inconsistent state. Capture more, or restructure.
6. Related Patterns
- Memento vs Command — these compose into full undo/redo:
- Memento snapshots state at a point in time. Restore = "go back to that state". Trivial to implement undo, expensive in memory for large state.
- Command records the action (what changed, with enough info to invert). Undo = inverse action. Cheap in memory, but every command must be reversible.
- Hybrid — Command stores a Memento of pre-state. Undo restores Memento; redo re-executes Command. Best of both: undo is robust (just restore), redo is replay-able.
- Memento vs Prototype — Prototype clones an object to make a new one; Memento clones state to restore the same one. Mechanically similar (
clone()), intentionally different. - Memento vs Iterator — robust iterators can store position as a Memento, surviving aggregate mutations.
- Memento vs Snapshot in DBMS — same idea at infrastructure scale (MVCC, savepoints).
- Memento vs Serialization — overlap: a Memento serialized to disk is a save-file. Pattern abstracts the interface (
save/restore); serialization concerns the encoding.
7. References
- Design Patterns (GoF), ch. 5.
- Refactoring.Guru — Memento
- cppreference —
frienddeclaration - Qt
QUndoCommand— Command + Memento hybrid in a real toolkit. - MVCC (multiversion concurrency control) — Memento at database scale.