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

  • 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 are private. 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