直接用 stack 会内存泄漏,因为 std::stack 仅管理指针本身,不释放其所指向的堆对象;若 push 的是 new command(),pop 后指针丢失而对象残留,导致内存泄漏。

为什么直接用 stack 会内存泄漏
因为 std::stack 只管理指针本身,不负责释放指针指向的对象。如果你 push 进去的是 new Command(),pop 时只丢掉指针值,对象还在堆上挂着。
实操建议:
- 改用
std::stack<:unique_ptr>></:unique_ptr>,让智能指针自动析构 - 或者手动在
undo()/redo()里调用delete cmd—— 但必须确保每个命令只被 delete 一次,且不能重复 pop 后再用 - 更稳妥的做法是:命令执行后立即移交所有权,比如
undoStack.push(std::move(cmd))
Command 基类必须有 virtual destructor 吗
必须。否则通过 Command* 指针 delete 子类对象(如 DeleteNodeCommand)时,只会调用基类析构函数,子类成员(比如动态分配的 backupData)不会被清理,导致资源泄漏或未定义行为。
常见错误现象:~Command() is not virtual 虽然编译通过,但运行时可能 crash 或内存残留。
正确写法:
class Command {
public:
virtual ~Command() = default; // 关键
virtual void execute() = 0;
virtual void undo() = 0;
};
undo/redo 时怎么避免重复执行或状态错乱
核心是维护两个栈 + 一个「当前编辑位置」概念。不是简单地 push/pop,而是要保证:执行新命令 → 清空 redo 栈;undo 后新命令 → 清空 redo 栈;redo 后新命令 → 清空 redo 栈中已过期的部分。
使用场景举例:用户连续输入“abc”,按了两次 Ctrl+Z(undo),再输入“x”,此时之前的“c”和“b”对应的 redo 必须失效。
实操要点:
- 每次调用
execute()新命令前,清空redoStack -
undo()弹出命令、执行undo()、再 push 到redoStack -
redo()弹出命令、执行execute()、再 push 到undoStack - 不要在命令内部修改全局状态(如静态计数器),所有状态变更必须封装在
execute()/undo()中
如何让 Command 支持序列化以实现持久化 Undo 日志
命令本身不保存完整数据快照(太重),而是保存「可逆操作描述」:比如 SetPropertyCommand{"user.name", "old", "new"},比深拷贝整个 User 对象轻量得多。
性能影响:如果命令携带大量数据(如图像编辑中的像素 diff),要考虑用 std::shared_ptr 共享备份缓冲区,避免重复拷贝。
兼容性注意点:
- 命令类需支持无参构造 + 成员序列化(比如用 nlohmann/json 的
NLOHMANN_DEFINE_TYPE_INTRUSIVE) - 反序列化后必须能安全调用
execute(),意味着所有依赖对象(如文档指针)需在执行前注入,不能存在裸指针悬空 - 建议把「执行上下文」抽成
CommandContext类传入,而不是让命令持有Document*
容易被忽略的地方是:撤销栈里的命令生命周期可能远长于原始编辑上下文,硬编码对象地址必然崩溃。真正健壮的实现,命令只存 ID、索引、差值,所有实体访问都通过回调或上下文代理。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











