C++备忘录模式保存恢复状态
C++备忘录模式:别让对象“失忆”,手把手存档+回滚状态
写过游戏存档功能吗?改完配置又想一键退回上一步?或者调试时反复试错,却总得手动重置一堆变量?这些场景背后,其实都在呼唤一个轻量但关键的设计模式——备忘录(Memento)。它不炫技,不造轮子,就是老老实实帮你把对象某一刻的状态“拍张照”,需要时再原样还原。C++里实现它,既不用依赖第三方库,也不必大动架构,关键是搞清谁该负责拍照、谁保管底片、谁来冲洗。
先说个常见误区:很多人一上来就想着用 std::vector<char> 或 std::string 把整个对象内存 dump 出来。这看似省事,实则埋雷——成员变量顺序、对齐填充、指针有效性、动态分配资源全被忽略。一旦类里有 std::string、std::vector 或自定义析构逻辑,直接 memcpy 就是给自己挖坑。
真正靠谱的做法,是让对象自己决定“哪些状态值得存”。比如一个文本编辑器类:
class TextEditor {
private:
std::string content_;
size_t cursor_pos_;
bool is_dirty_;
public:
// 备忘录类:只暴露读取接口,禁止外部修改
class Memento {
friend class TextEditor;
std::string content_;
size_t cursor_pos_;
explicit Memento(std::string c, size_t pos)
: content_(std::move(c)), cursor_pos_(pos) {}
public:
// 只读访问,不提供 setter
const std::string& content() const { return content_; }
size_t cursor_position() const { return cursor_pos_; }
};
// 创建快照:只保存核心状态,跳过临时计算字段(如 is_dirty_)
Memento save() const {
return Memento{content_, cursor_pos_};
}
// 恢复状态:用快照重建内部数据
void restore(const Memento& m) {
content_ = m.content();
cursor_pos_ = m.cursor_position();
is_dirty_ = true; // 恢复后视为已修改
}
// 其他业务方法...
void insert(char c) { /* ... */ }
void move_cursor(size_t pos) { /* ... */ }
};
注意三个细节:
- Memento 是 TextEditor 的嵌套类,且构造函数私有,只有
TextEditor能创建它; - Memento 不暴露任何修改接口,避免外部篡改快照;
save()里刻意没存is_dirty_——它属于派生状态,恢复时重新设为true更合理。
那怎么管理多个快照?比如支持 Ctrl+Z 多步撤销。这时候加个简单的栈就行:
class EditorHistory {
private:
std::stack<TextEditor::Memento> snapshots_;
public:
void push(const TextEditor::Memento& m) {
snapshots_.push(m);
}
bool can_undo() const { return !snapshots_.empty(); }
TextEditor::Memento pop() {
auto m = std::move(snapshots_.top());
snapshots_.pop();
return m;
}
};
用的时候很直白:
TextEditor editor;
EditorHistory history;
editor.insert('H'); editor.insert('e'); // "He"
history.push(editor.save()); // 存档点1
editor.insert('l'); editor.insert('l'); // "Hell"
history.push(editor.save()); // 存档点2
editor.insert('o'); // "Hello"
if (history.can_undo()) {
editor.restore(history.pop()); // 回退到 "Hell"
}
这里有个实用技巧:快照不必每次都深拷贝全部数据。如果 content_ 很长但多数时候不变,可以考虑用 std::shared_ptr<const std::string> 来共享只读内容,save() 时只增加引用计数——既省内存,又避免重复分配。当然,前提是确认字符串不会被意外修改(const 保证 + 移动语义配合)。
再聊个真实痛点:带资源的对象怎么存? 比如一个音频播放器持有 ALuint buffer_id(OpenAL 音频缓冲区)。这种句柄不能直接序列化,恢复时得重新绑定资源。正确做法是:快照里只存逻辑状态(如当前播放时间、音量、曲目ID),资源句柄由播放器在 restore() 时按需重建。把“状态”和“资源”解耦,模式才真正健壮。
最后提醒一句:备忘录不是银弹。频繁调用 save() 会吃内存,尤其状态庞大时。实际项目中,建议结合业务节奏做节流——比如编辑器只在用户显式触发(Ctrl+S)或每5秒自动存一次,而不是每次按键都快照。
写完这段代码,我顺手给本地一个配置解析器加了备忘录支持。昨天误删了一段 YAML 后,三秒就从历史快照里捞回来了。没有魔法,只是把“那一刻的样子”稳稳接住,等你回头要它的时候,它还在那儿。
备忘录模式的价值,从来不在多酷,而在于让你敢改、敢试、敢推倒重来——因为你知道,那个“之前”,一直被好好存着。


还没有评论,来说两句吧...