备忘录模式核心结构为Memento(私有状态载体,仅Originator友元访问)、Originator(提供save/restore接口)和Caretaker(仅存储传递Memento,不访问其内部)。

备忘录模式的核心结构怎么写
备忘录模式本质是把对象的内部状态快照分离出去,不破坏封装的前提下支持撤销/恢复。C++里必须用三个角色配合:Memento(只读状态载体)、Originator(被备份的对象)、Caretaker(管理快照生命周期)。关键不是“存”,而是“不让外部修改状态”——所以Memento的构造函数和成员变量必须是private,仅对Originator开放友元。
常见错误是把Memento设计成公开的结构体,导致外部能直接改状态,彻底失去封装意义;或者让Caretaker持有Originator*并调用其非const方法来恢复,绕过了状态隔离。
-
Memento类不提供任何set接口,只允许Originator通过友元访问其私有数据 -
Originator::save()返回Memento值对象(不是指针),避免生命周期纠缠 -
Caretaker只负责存储和传递Memento,绝不访问其内部字段
如何避免深拷贝开销和内存泄漏
如果Originator内部有大块动态内存(比如std::vector缓存),每次save()都做完整深拷贝会很慢。这时可以用std::shared_ptr包裹状态数据,在Memento中只存引用计数指针,但要注意:恢复时不能简单赋值指针,得确保Originator原有资源被正确释放。
更稳妥的做法是用移动语义:Originator::save()把当前状态std::move进Memento,而restore()再把状态std::move回来。这样零拷贝,且明确转移所有权。
- 不要在
Memento里存裸指针或std::unique_ptr——恢复时可能悬空或重复释放 - 若状态含
std::string/std::vector,默认拷贝已足够高效,无需过早优化 - 测试时故意在
save()后修改Originator,再restore(),确认原始状态确实被还原
多级撤销怎么安全管理Memento生命周期
用户连续操作10次,你存了10个Memento,但第3次撤销后又新操作,此时第4–10个快照应失效。常见错误是用std::vector<:unique_ptr>>存所有快照,却没在新操作时清空后续元素,导致内存泄漏+逻辑错乱。
正确做法是用std::vector(值语义),并在Caretaker::addMemento()里先erase掉当前索引之后的所有元素,再push_back。这样既避免指针管理负担,又保证快照链严格线性。
- 不要用
std::list——随机访问撤销位置(如“撤回到第5步”)会变慢 - 如果担心频繁
erase开销,可用两个std::vector分段管理(历史区/未来区),但多数场景没必要 - 记得给
Memento加explicit构造函数,防止隐式转换干扰容器操作
为什么operator=和拷贝构造函数要特别处理
Memento本身是值类型,但它的存在意义是“冻结某一时刻的状态”。如果用户意外写了Memento a = b;,而b是旧快照,a可能被误当成新快照继续保存——这会污染撤销栈。所以Memento应显式删除拷贝赋值和拷贝构造(除非你真需要复制快照)。
更隐蔽的问题是Originator的拷贝控制:如果Originator可拷贝,它的save()返回的Memento是否应该绑定到原对象?答案是否定的——Memento只记录状态,和哪个Originator实例无关。
- 在
Memento类里声明Memento(const Memento&) = delete;和Memento& operator=(const Memento&) = delete; -
Originator的拷贝构造函数不应自动调用save()——快照是主动行为,不是构造附带结果 - 如果编译报错说“use of deleted function”,说明你某处隐式触发了拷贝,顺着栈回溯定位即可
真正难的不是写三个类,而是守住“状态不可变”这条线——哪怕一个public成员变量、一次忘记const限定、一处裸指针传递,都会让整个模式失效。写完立刻用ASan跑一遍,重点看恢复后对象是否真的回到原状。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











