如何在c++中实现一个简单的命令(Command)设计模式? (使用std::function)

12次阅读

用std::function实现Command更轻量,因其无需定义命令类继承体系,可直接捕获Lambda、函数或成员函数;可撤销Command需同时存储execute_fn和undo_fn两个std::function,绑定成员函数推荐用lambda并注意捕获方式。

如何在c++中实现一个简单的命令(Command)设计模式? (使用std::function)

为什么std::function 实现 Command 比虚函数更轻量?

因为不用定义一具体命令类和继承体系,std::function 能直接捕获 lambda、普通函数或成员函数,把“执行逻辑”变成一等公民。适合配置驱动、脚本化或临时任务调度场景,比如 ui 按钮绑定、宏录制回放、Undo 的简单实现。

但要注意:它不自带 undo() 或元信息(如命令名),若需这些,得额外包装一层结构体

如何构造一个可撤销的 Command 类?

std::function 存执行逻辑,再加一个 std::function 存撤销逻辑。两者都支持捕获上下文,避免裸指针生命周期风险。

  • 撤销函数必须与执行函数语义对称,例如执行时 balance += 100,撤销时就得 balance -= 100
  • 避免在 lambda 中捕获局部变量的引用(除非确保生命周期足够长),优先用值捕获或 shared_ptr
  • 如果撤销逻辑不存在,可设为默认空函数:std::function undo_fn = []{};
struct Command {     std::function execute_fn;     std::function undo_fn; 
void execute() { execute_fn(); } void undo() { undo_fn(); }

};

立即学习C++免费学习笔记(深入)”;

怎么把成员函数绑定成 Command

不能直接传 &MyClass::doSave,必须绑定对象实例。用 std::bind 或 lambda 更直观,lambda 更推荐——类型推导干净、无隐藏拷贝开销。

  • 错误写法:Command cmd{&MyClass::doSave, &MyClass::undoSave}; —— 缺少对象,编译失败
  • 正确写法(lambda):[obj](){ obj.doSave(); },注意值捕获 obj 还是引用捕获 &obj,取决于是否需要修改原对象
  • 若对象是动态分配的,建议用 std::shared_ptr 捕获,防止悬垂
class Editor { public:     void save() { /* ... */ }     void undo_save() { /* ... */ } }; 

Editor editor; auto cmd = Command{ [&editor]() { editor.save(); }, [&editor]() { editor.undo_save(); } };

执行队列和 Undo 怎么配合 std::function

std::vector 当命令历史,每次 execute() 后 push,undo() 时 pop 并调用其 undo_fn。关键点在于:不要在 Command 构造后修改被捕获对象的状态,否则 undo 行为不可预测。

常见坑是多次 undo 后又 redo —— std::function 版本默认不支持 redo,除非你额外维护一个重做栈,或让每个 Command 自带 redo_fn 字段。

另外,std::function 有约 16–32 字节的额外开销(取决于实现),高频短命命令(如每帧几十个)要考虑是否值得;此时原始函数指针 + void* 上下文可能更高效。

text=ZqhQzanResources