命令对象必须保存执行前的状态才能撤销,需在execute()前用深拷贝保存目标对象快照,撤销栈用std::stack配合std::unique_ptr管理,command接口必须含virtual析构函数,重复执行与重做需状态标记。

命令对象必须保存执行前的状态才能撤销
撤销不是自动发生的,C++里没有内置的“回退栈”机制。关键在于:每次 execute() 前,命令对象得自己记下被修改对象的关键状态(比如数值、字符串、容器内容等),否则 undo() 没东西可还原。
常见错误是只保存操作参数(如“把 x 加 5”),但没存 x 原来的值——结果 undo() 只能再减 5,一旦中间被其他操作改过 x,就失效了。
- 推荐在命令构造时或
execute()开头用深拷贝保存目标对象的快照(std::string、std::vector等支持直接赋值) - 避免保存裸指针或引用;若目标是大型对象,考虑用
std::shared_ptr管理快照,减少拷贝开销 - 对简单类型(
int、bool),直接存值即可:int m_prev_value;
撤销栈要用 std::stack<:unique_ptr>> 管理
用 std::stack 存命令指针最自然,但必须用 std::unique_ptr 而不是原始指针——否则 pop() 后内存泄漏,或者多个地方误删同一对象。
注意:命令执行后才入栈,不是构造后就入栈;否则未执行的命令也被允许撤销,逻辑错乱。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
-
execute()成功返回 true 后,才调history.push(std::move(cmd)) -
undo()从栈顶取命令、调undo()、再pop();失败时不弹栈,保持一致性 - 不要用
std::vector模拟栈——erase(begin())效率低,且易误用索引
Command 接口必须定义虚析构函数
否则通过基类指针(Command*)删除派生类对象时,派生部分不会被析构,造成资源泄漏或未定义行为——这是 C++ 多态撤销模式里最常踩的坑。
哪怕命令类只存几个 int,只要用了多态,就必须加 virtual ~Command() = default;。
- 接口定义示例:
class Command {<br>public:<br> virtual ~Command() = default;<br> virtual void execute() = 0;<br> virtual void undo() = 0;<br>}; - 如果命令类带资源(文件句柄、动态分配内存),在
undo()或析构里释放,别依赖execute()清理 - 不加
virtual析构函数,std::unique_ptr<command></command>在销毁时也会跳过派生类析构
重复执行和重做(redo)需要额外状态标记
标准命令模式只解决单向撤销。如果用户执行 A → B → undo → redo,就需要记录命令是否已执行过、是否已被撤销,否则 redo() 可能重复执行或执行已失效的操作。
最简方案是在命令类里加 bool m_executed{false}; 和 bool m_undone{false};,并在 execute()/undo() 中更新它们。
-
execute()先检查!m_executed,成功后设为true -
undo()先检查m_executed && !m_undone,成功后设m_undone = true -
redo()是undo()的逆操作,需判断m_undone并重置两个标记 - 更健壮的做法是引入状态枚举:
enum class State { Pending, Executed, Undone };
undo() 后,手动改一次目标对象再撤销,看是否真还原了。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










