1. 备忘录模式的核心概念与应用场景
备忘录模式(Memento Pattern)是行为型设计模式中的一种,它允许在不破坏封装性的前提下,捕获并保存一个对象的内部状态,以便在需要时能够恢复到这个状态。这个模式在C++开发中尤为实用,特别是在需要实现撤销操作、历史记录或状态回滚的场景中。
想象一下你在使用文本编辑器时的场景:每次按下Ctrl+Z撤销操作时,编辑器都能准确地回到之前的状态,这正是备忘录模式的典型应用。在游戏开发中,存档/读档功能也是备忘录模式的体现——游戏状态被完整保存,玩家可以随时回到之前的游戏进度。
备忘录模式由三个核心角色组成:
- Originator(发起人):需要保存状态的对象
- Memento(备忘录):存储Originator内部状态的对象
- Caretaker(管理者):负责保存和管理Memento对象
在C++实现中,我们通常会遇到几个关键问题:如何设计Memento类的接口以保持封装性?如何管理多个状态的历史记录?以及如何处理大型对象的深拷贝问题?这些都是我们在实现备忘录模式时需要仔细考虑的设计决策。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. C++实现备忘录模式的基本结构
让我们从一个最简单的文本编辑器撤销功能示例开始,逐步构建备忘录模式的完整实现。这个例子将展示如何在C++中正确应用该模式。
2.1 定义Originator类
cpp复制class TextEditor {
private:
std::string content;
public:
// 创建备忘录,保存当前状态
class Memento {
private:
std::string state;
friend class TextEditor; // 允许TextEditor访问私有成员
Memento(const std::string& s) : state(s) {}
public:
std::string getState() const { return state; }
};
// 设置内容
void setContent(const std::string& newContent) {
content = newContent;
}
// 获取当前内容
std::string getContent() const {
return content;
}
// 创建备忘录
Memento createMemento() const {
return Memento(content);
}
// 从备忘录恢复
void restoreFromMemento(const Memento& m) {
content = m.getState();
}
};
这个实现有几个值得注意的设计点:
- 将Memento类作为TextEditor的内部类,保持了良好的封装性
- 使用friend声明允许TextEditor访问Memento的私有成员
- Memento的构造函数是私有的,只能由TextEditor创建
2.2 实现Caretaker类
cpp复制class History {
private:
std::vector<TextEditor::Memento> states;
public:
void push(const TextEditor::Memento& m) {
states.push_back(m);
}
TextEditor::Memento pop() {
if (states.empty()) {
throw std::runtime_error("No states to restore");
}
auto last = states.back();
states.pop_back();
return last;
}
bool isEmpty() const {
return states.empty();
}
};
History类作为Caretaker角色,负责管理备忘录对象的生命周期。这里使用了简单的栈结构来保存历史状态,符合撤销操作的LIFO(后进先出)特性。
2.3 完整使用示例
cpp复制int main() {
TextEditor editor;
History history;
// 编辑文本并保存状态
editor.setContent("First version");
history.push(editor.createMemento());
editor.setContent("Second version");
history.push(editor.createMemento());
// 执行撤销操作
if (!history.isEmpty()) {
editor.restoreFromMemento(history.pop());
std::cout << "After undo: " << editor.getContent() << std::endl;
// 输出: After undo: Second version
}
if (!history.isEmpty()) {
editor.restoreFromMemento(history.pop());
std::cout << "After second undo: " << editor.getContent() << std::endl;
// 输出: After second undo: First version
}
return 0;
}
这个基础实现展示了备忘录模式的核心机制,但在实际项目中,我们还需要考虑更多复杂情况,比如内存管理、大对象的状态保存效率等。
3. 备忘录模式的高级实现技巧
在实际的C++项目中,简单的备忘录模式实现可能会遇到性能问题或设计上的挑战。下面我们来探讨几个高级实现技巧。
3.1 增量式状态保存
对于大型对象,完整保存所有状态可能会消耗大量内存。这时可以采用增量式保存策略:
cpp复制class GraphicEditor {
private:
std::vector<GraphicObject> objects;
// 其他状态...
public:
class Memento {
private:
// 只保存变化的部分
ChangeSet changes;
friend class GraphicEditor;
Memento(const ChangeSet& cs) : changes(cs) {}
};
Memento createMemento() const {
// 只计算并保存自上次保存以来的变化
ChangeSet changes = calculateChangesSinceLastSave();
return Memento(changes);
}
void restoreFromMemento(const Memento& m) {
applyChanges(m.changes);
}
};
这种实现显著减少了内存使用,特别适合图形编辑器这类状态复杂的应用。
3.2 使用智能指针管理备忘录
在C++中,手动管理备忘录对象的生命周期容易出错。我们可以利用智能指针来自动管理:
cpp复制class History {
private:
std::vector<std::shared_ptr<TextEditor::Memento>> states;
public:
void push(std::shared_ptr<TextEditor::Memento> m) {
states.push_back(m);
}
std::shared_ptr<TextEditor::Memento> pop() {
if (states.empty()) {
throw std::runtime_error("No states to restore");
}
auto last = states.back();
states.pop_back();
return last;
}
};
使用shared_ptr可以避免内存泄漏,同时在多个地方共享备忘录对象时也能保证安全性。
3.3 序列化支持
对于需要持久化保存的状态,我们可以扩展备忘录模式支持序列化:
cpp复制class SerializableMemento {
public:
virtual ~SerializableMemento() = default;
virtual std::string serialize() const = 0;
virtual void deserialize(const std::string& data) = 0;
};
class TextEditorMemento : public SerializableMemento {
private:
std::string state;
public:
std::string serialize() const override {
// 实现序列化逻辑
return state; // 简单示例,实际可能需要更复杂的序列化
}
void deserialize(const std::string& data) override {
state = data;
}
std::string getState() const { return state; }
};
这样我们可以将备忘录保存到文件或数据库中,实现持久的撤销历史。
4. 备忘录模式在C++项目中的实际应用
备忘录模式在C++项目中有着广泛的应用场景,下面我们来看几个典型用例。
4.1 游戏状态保存与恢复
在游戏开发中,备忘录模式常用于实现存档/读档功能:
cpp复制class GameState {
private:
PlayerState player;
WorldState world;
// 其他游戏状态...
public:
class GameMemento {
private:
PlayerState playerState;
WorldState worldState;
friend class GameState;
GameMemento(const PlayerState& ps, const WorldState& ws)
: playerState(ps), worldState(ws) {}
public:
const PlayerState& getPlayerState() const { return playerState; }
const WorldState& getWorldState() const { return worldState; }
};
GameMemento save() const {
return GameMemento(player, world);
}
void load(const GameMemento& m) {
player = m.getPlayerState();
world = m.getWorldState();
}
};
游戏引擎可以定期自动创建备忘录,或者在关键点(如关卡开始)手动创建,为玩家提供灵活的存档选择。
4.2 事务性操作支持
在数据库或文件系统操作中,备忘录模式可以实现事务回滚:
cpp复制class DatabaseTransaction {
private:
Database& db;
std::vector<Database::Memento> states;
public:
explicit DatabaseTransaction(Database& database) : db(database) {
states.push_back(db.createMemento());
}
void commit() {
states.clear();
}
void rollback() {
if (!states.empty()) {
db.restoreFromMemento(states.back());
states.clear();
}
}
void savepoint() {
states.push_back(db.createMemento());
}
};
这种实现允许嵌套的事务操作,每个savepoint()调用都会创建一个新的恢复点。
4.3 配置管理
在应用程序配置管理中,备忘录模式可以实现配置的撤销/重做:
cpp复制class AppConfig {
private:
std::map<std::string, std::string> settings;
public:
class ConfigMemento {
private:
std::map<std::string, std::string> state;
friend class AppConfig;
ConfigMemento(const std::map<std::string, std::string>& s) : state(s) {}
public:
const std::map<std::string, std::string>& getState() const { return state; }
};
ConfigMemento save() const {
return ConfigMemento(settings);
}
void restore(const ConfigMemento& m) {
settings = m.getState();
}
// 其他配置操作方法...
};
这对于需要频繁调整参数的应用(如图形渲染设置)特别有用。
5. 备忘录模式的性能优化与陷阱规避
在实际使用备忘录模式时,我们需要考虑性能问题和潜在的设计陷阱。
5.1 大对象的状态保存优化
当Originator对象很大时,完整保存状态会消耗大量内存。可以采用以下优化策略:
- 差分备忘录:只保存变化的部分
- 懒复制:使用写时复制技术
- 外部存储:将不常用的历史状态保存到磁盘
cpp复制class LargeObjectMemento {
private:
std::shared_ptr<LargeData> data; // 共享数据指针
ChangeSet changes; // 差异部分
public:
LargeObjectMemento(std::shared_ptr<LargeData> base, const ChangeSet& cs)
: data(base), changes(cs) {}
void restore(LargeObject& obj) const {
obj.resetToBase(data);
obj.applyChanges(changes);
}
};
5.2 深拷贝与浅拷贝问题
C++中默认的拷贝是浅拷贝,对于包含指针或动态分配资源的对象,需要特别注意:
cpp复制class Document {
private:
std::vector<std::unique_ptr<Element>> elements;
public:
class DocumentMemento {
private:
std::vector<std::unique_ptr<Element>> elementsCopy;
// 实现深拷贝
static std::vector<std::unique_ptr<Element>> deepCopy(
const std::vector<std::unique_ptr<Element>>& src) {
std::vector<std::unique_ptr<Element>> dest;
for (const auto& elem : src) {
dest.push_back(std::make_unique<Element>(*elem));
}
return dest;
}
public:
DocumentMemento(const std::vector<std::unique_ptr<Element>>& elems)
: elementsCopy(deepCopy(elems)) {}
std::vector<std::unique_ptr<Element>> getState() const {
return deepCopy(elementsCopy);
}
};
};
5.3 内存管理策略
根据使用场景选择合适的智能指针:
- 独占所有权:使用std::unique_ptr
- 共享所有权:使用std::shared_ptr
- 弱引用:使用std::weak_ptr避免循环引用
cpp复制class History {
private:
std::vector<std::weak_ptr<Memento>> states; // 避免强引用保持备忘录存活
public:
void add(std::shared_ptr<Memento> m) {
states.push_back(m);
}
std::shared_ptr<Memento> getLatest() const {
if (states.empty()) return nullptr;
return states.back().lock();
}
};
5.4 线程安全考虑
在多线程环境中使用备忘录模式需要额外的同步措施:
cpp复制class ThreadSafeOriginator {
private:
std::mutex mtx;
State state;
public:
class Memento {
State state;
public:
Memento(const State& s) : state(s) {}
State getState() const { return state; }
};
Memento save() const {
std::lock_guard<std::mutex> lock(mtx);
return Memento(state);
}
void restore(const Memento& m) {
std::lock_guard<std::mutex> lock(mtx);
state = m.getState();
}
};
6. 备忘录模式与其他设计模式的结合
备忘录模式常与其他设计模式结合使用,形成更强大的解决方案。
6.1 与命令模式结合
命令模式+备忘录模式可以实现功能强大的撤销/重做系统:
cpp复制class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual void undo() = 0;
virtual std::shared_ptr<Memento> createMemento() const = 0;
};
class TextInsertCommand : public Command {
private:
TextEditor& editor;
std::string text;
size_t position;
std::shared_ptr<Memento> backup;
public:
TextInsertCommand(TextEditor& e, const std::string& t, size_t pos)
: editor(e), text(t), position(pos) {}
void execute() override {
backup = std::make_shared<Memento>(editor.createMemento());
editor.insertText(text, position);
}
void undo() override {
if (backup) {
editor.restoreFromMemento(*backup);
}
}
std::shared_ptr<Memento> createMemento() const override {
return backup;
}
};
6.2 与原型模式结合
当需要频繁创建相似备忘录时,可以使用原型模式提高效率:
cpp复制class MementoPrototype {
public:
virtual ~MementoPrototype() = default;
virtual std::unique_ptr<MementoPrototype> clone() const = 0;
};
class ConcreteMemento : public MementoPrototype {
private:
State state;
public:
ConcreteMemento(const State& s) : state(s) {}
std::unique_ptr<MementoPrototype> clone() const override {
return std::make_unique<ConcreteMemento>(*this);
}
State getState() const { return state; }
};
6.3 与观察者模式结合
实现状态变化的通知机制:
cpp复制class ObservableOriginator {
private:
State state;
std::vector<std::function<void(const Memento&)>> observers;
public:
void addObserver(std::function<void(const Memento&)> observer) {
observers.push_back(observer);
}
void changeState(const State& newState) {
auto memento = createMemento();
state = newState;
notifyObservers(memento);
}
Memento createMemento() const {
return Memento(state);
}
private:
void notifyObservers(const Memento& m) const {
for (const auto& observer : observers) {
observer(m);
}
}
};
7. C++17/20新特性在备忘录模式中的应用
现代C++的新特性可以让备忘录模式的实现更加简洁和安全。
7.1 使用std::variant实现类型安全的备忘录
cpp复制class MultiStateOriginator {
private:
std::variant<StateA, StateB, StateC> state;
public:
class Memento {
private:
std::variant<StateA, StateB, StateC> savedState;
public:
template<typename T>
Memento(T&& state) : savedState(std::forward<T>(state)) {}
auto getState() const { return savedState; }
};
Memento save() const {
return std::visit([](auto&& arg) -> Memento {
return Memento(arg);
}, state);
}
void restore(const Memento& m) {
state = m.getState();
}
};
7.2 使用std::optional处理无效状态
cpp复制class OptionalMementoHistory {
private:
std::vector<std::optional<Memento>> states;
public:
void push(Memento m) {
states.push_back(std::make_optional(std::move(m)));
}
std::optional<Memento> pop() {
if (states.empty()) return std::nullopt;
auto m = std::move(states.back());
states.pop_back();
return m;
}
};
7.3 使用移动语义优化性能
cpp复制class MoveOptimizedMemento {
private:
std::unique_ptr<LargeData> data;
public:
MoveOptimizedMemento(std::unique_ptr<LargeData> d) : data(std::move(d)) {}
MoveOptimizedMemento(MoveOptimizedMemento&&) = default;
MoveOptimizedMemento& operator=(MoveOptimizedMemento&&) = default;
std::unique_ptr<LargeData> getData() && {
return std::move(data);
}
const LargeData& getData() const & {
return *data;
}
};
8. 备忘录模式的测试与调试技巧
确保备忘录模式正确实现需要特定的测试策略。
8.1 单元测试备忘录功能
cpp复制TEST(TextEditorMemento, SaveRestore) {
TextEditor editor;
editor.setContent("Test content");
auto memento = editor.createMemento();
editor.setContent("Modified content");
editor.restoreFromMemento(memento);
ASSERT_EQ(editor.getContent(), "Test content");
}
8.2 测试撤销/重做序列
cpp复制TEST(History, UndoRedoSequence) {
TextEditor editor;
History history;
editor.setContent("State 1");
history.push(editor.createMemento());
editor.setContent("State 2");
history.push(editor.createMemento());
editor.restoreFromMemento(history.pop());
ASSERT_EQ(editor.getContent(), "State 2");
editor.restoreFromMemento(history.pop());
ASSERT_EQ(editor.getContent(), "State 1");
ASSERT_TRUE(history.isEmpty());
}
8.3 性能测试与优化
cpp复制TEST(MementoPerformance, LargeStateSave) {
LargeObject obj;
// 初始化大型状态...
auto start = std::chrono::high_resolution_clock::now();
auto memento = obj.createMemento();
auto end = std::chrono::high_resolution_clock::now();
auto duration = end - start;
ASSERT_LT(duration, std::chrono::milliseconds(100));
}
8.4 内存泄漏检测
cpp复制TEST(MementoMemory, NoLeaks) {
auto tracker = MemoryTracker::start();
{
Originator originator;
Caretaker caretaker;
caretaker.save(originator.createMemento());
originator.modifyState();
caretaker.save(originator.createMemento());
}
ASSERT_EQ(tracker.getLeakCount(), 0);
}
9. 备忘录模式在C++项目中的最佳实践
基于多年C++开发经验,我总结了以下备忘录模式的最佳实践:
-
明确生命周期管理:确定备忘录对象的生命周期由谁负责,是Originator、Caretaker还是共享所有权。
-
控制历史深度:实现历史记录的最大数量限制,防止内存无限增长。
cpp复制class BoundedHistory {
private:
std::deque<Memento> states;
size_t maxSize;
public:
explicit BoundedHistory(size_t size) : maxSize(size) {}
void push(Memento m) {
if (states.size() >= maxSize) {
states.pop_front();
}
states.push_back(std::move(m));
}
Memento pop() {
if (states.empty()) throw std::runtime_error("Empty history");
auto m = std::move(states.back());
states.pop_back();
return m;
}
};
-
考虑序列化格式:如果需要持久化备忘录,选择高效的序列化格式(如Protocol Buffers、MessagePack等)。
-
版本兼容性:为备忘录数据添加版本号,处理不同版本软件间的兼容性问题。
cpp复制class VersionedMemento {
private:
uint32_t version;
std::vector<uint8_t> data;
public:
static constexpr uint32_t CURRENT_VERSION = 1;
VersionedMemento(uint32_t ver, std::vector<uint8_t> d)
: version(ver), data(std::move(d)) {}
bool isCompatible() const {
return version == CURRENT_VERSION;
}
// 其他方法...
};
- 异常安全:确保备忘录操作不会破坏Originator的状态,即使在异常情况下。
cpp复制class ExceptionSafeOriginator {
private:
State state;
public:
Memento save() const noexcept {
try {
return Memento(state);
} catch (...) {
// 返回一个有效的空状态备忘录
return Memento(State());
}
}
void restore(const Memento& m) {
auto oldState = state; // 先保存当前状态
try {
state = m.getState();
} catch (...) {
state = oldState; // 恢复原状态
throw; // 重新抛出异常
}
}
};
-
文档化备忘录格式:为备忘录的内部数据结构编写详细文档,便于后续维护和扩展。
-
性能监控:在关键操作中添加性能监控点,确保备忘录操作不会成为性能瓶颈。
cpp复制class MonitoredOriginator {
private:
State state;
PerformanceMonitor& monitor;
public:
Memento save() const {
auto timer = monitor.startTimer("MementoSave");
auto m = Memento(state);
timer.stop();
return m;
}
void restore(const Memento& m) {
auto timer = monitor.startTimer("MementoRestore");
state = m.getState();
timer.stop();
}
};
10. 备忘录模式的替代方案与变体
虽然备忘录模式很强大,但在某些场景下可能有更适合的替代方案。
10.1 命令模式实现撤销/重做
cpp复制class UndoableCommand {
public:
virtual ~UndoableCommand() = default;
virtual void execute() = 0;
virtual void undo() = 0;
};
class TextInsertCommand : public UndoableCommand {
private:
TextEditor& editor;
std::string text;
size_t position;
public:
TextInsertCommand(TextEditor& e, const std::string& t, size_t pos)
: editor(e), text(t), position(pos) {}
void execute() override {
editor.insertText(text, position);
}
void undo() override {
editor.deleteText(position, text.length());
}
};
10.2 持久化数据结构
使用不可变数据结构实现高效的状态管理:
cpp复制class PersistentTextDocument {
private:
std::shared_ptr<const std::string> content;
public:
PersistentTextDocument() : content(std::make_shared<const std::string>("")) {}
PersistentTextDocument insertText(size_t pos, const std::string& text) const {
auto newStr = std::make_shared<std::string>(*content);
newStr->insert(pos, text);
return PersistentTextDocument(newStr);
}
PersistentTextDocument deleteText(size_t pos, size_t len) const {
auto newStr = std::make_shared<std::string>(*content);
newStr->erase(pos, len);
return PersistentTextDocument(newStr);
}
private:
explicit PersistentTextDocument(std::shared_ptr<const std::string> c)
: content(std::move(c)) {}
};
10.3 事件溯源模式
记录状态变化事件而非完整状态:
cpp复制class EventSourcedAccount {
private:
int balance = 0;
std::vector<std::unique_ptr<AccountEvent>> events;
public:
void deposit(int amount) {
if (amount <= 0) throw std::invalid_argument("Amount must be positive");
apply(std::make_unique<DepositEvent>(amount));
}
void withdraw(int amount) {
if (amount <= 0) throw std::invalid_argument("Amount must be positive");
if (amount > balance) throw std::runtime_error("Insufficient funds");
apply(std::make_unique<WithdrawalEvent>(amount));
}
void restoreFromHistory(const std::vector<std::unique_ptr<AccountEvent>>& history) {
balance = 0;
events.clear();
for (const auto& event : history) {
apply(event->clone());
}
}
private:
void apply(std::unique_ptr<AccountEvent> event) {
event->apply(balance);
events.push_back(std::move(event));
}
};
10.4 差异备忘录变体
只保存状态变化的部分:
cpp复制class DiffMemento {
private:
using Diff = std::vector<std::byte>;
Diff difference;
public:
DiffMemento(const Diff& diff) : difference(diff) {}
const Diff& getDifference() const { return difference; }
};
class DiffBasedOriginator {
private:
State current;
State base;
public:
DiffMemento save() const {
auto diff = calculateDiff(base, current);
return DiffMemento(diff);
}
void restore(const DiffMemento& m) {
current = applyDiff(base, m.getDifference());
}
void commit() {
base = current;
}
};
在实际项目中,我经常需要根据具体需求在标准备忘录模式和这些变体之间做出选择。对于状态较小且变化频繁的场景,标准备忘录模式通常是最佳选择;而对于状态很大或需要长期历史记录的系统,则可能需要考虑差异备忘录或事件溯源等变体。
