命令模式下填充Map引发空指针访问违规的解决方法咨询
解决C++命令模式中Map空指针访问违规问题及优化方案
咱们先把问题根源掰明白:你这段代码里的add、sub、mul、div这些指针只是被声明了,从来没通过new创建对应的对象实例,所以commands map里存的全是空指针,调用execute的时候自然会触发内存访问错误。下面给你一步步讲修复方法,还有几种符合你需求的优化方案——不用全局变量,还能结合工厂模式解耦代码。
一、基础修复:先解决空指针问题
先把最核心的问题解决,正确实例化对象并填充map,同时补上一些容易忽略的细节(比如虚析构、异常检查):
#include <map> #include <stdexcept> #include <iostream> class ICommand { public: // 改成纯虚函数,强制子类必须实现execute virtual double execute(double, double) = 0; // 虚析构函数,确保子类对象能被正确销毁 virtual ~ICommand() = default; }; class Add: public ICommand { public: double execute(double a, double b) override { return a + b; } double operator()(double a, double b){ return a + b; } }; class Sub : public ICommand { public: double execute(double a, double b) override { return a - b; } double operator()(double a, double b) { return a - b; } }; class Mul : public ICommand { public: double execute(double a, double b) override { return a * b; } double operator()(double a, double b) { return a * b; } }; class Div : public ICommand { public: double execute(double a, double b) override { if (b == 0) throw std::invalid_argument("不能除以零"); return a / b; } double operator()(double a, double b) { if (b == 0) throw std::invalid_argument("不能除以零"); return a / b; } }; // 修正拼写错误:Hundler -> Handler class RequestHandler { std::map<int, ICommand*> commands; public: RequestHandler() { // 直接在构造函数里实例化每个命令对象,存入map commands[1] = new Add(); commands[2] = new Sub(); commands[3] = new Mul(); commands[4] = new Div(); } // 析构函数释放map里的所有对象,避免内存泄漏 ~RequestHandler() { for (auto& pair : commands) { delete pair.second; } commands.clear(); } double HandleRequest(int action, double a, double b) { auto it = commands.find(action); if (it == commands.end()) { throw std::invalid_argument("无效的操作码"); } return it->second->execute(a, b); } // 禁用拷贝构造和赋值,防止浅拷贝导致重复释放内存 RequestHandler(const RequestHandler&) = delete; RequestHandler& operator=(const RequestHandler&) = delete; }; // 修正main函数参数:第一个参数是int,不是double int main(int argc, char* argv[]) { // 栈上创建对象,比new更安全,不用手动delete RequestHandler handler; try { double result = handler.HandleRequest(2, 4, 6); std::cout << "计算结果:" << result << std::endl; // 输出-2 } catch (const std::exception& e) { std::cerr << "错误:" << e.what() << std::endl; } return 0; }
这里几个关键修复点:
- 给
ICommand添加了纯虚的execute和默认虚析构,保证接口规范性和子类析构正确。 - 构造函数里直接实例化命令对象并存入map,不再需要单独的成员指针。
- 添加了析构函数释放内存,同时禁用拷贝操作避免浅拷贝问题。
- 补上了无效命令、除零的异常检查,让代码更健壮。
二、优化:用智能指针避免手动内存管理
上面的方案需要手动管理内存,容易出错,用std::unique_ptr自动管理内存会更省心:
#include <map> #include <memory> #include <stdexcept> #include <iostream> class ICommand { public: virtual double execute(double, double) = 0; virtual ~ICommand() = default; }; // Add、Sub、Mul、Div类保持不变... class RequestHandler { // 用unique_ptr管理命令对象,自动释放内存 std::map<int, std::unique_ptr<ICommand>> commands; public: RequestHandler() { // 用make_unique直接构造对象存入map,无需手动delete commands[1] = std::make_unique<Add>(); commands[2] = std::make_unique<Sub>(); commands[3] = std::make_unique<Mul>(); commands[4] = std::make_unique<Div>(); } double HandleRequest(int action, double a, double b) { auto it = commands.find(action); if (it == commands.end()) { throw std::invalid_argument("无效的操作码"); } return it->second->execute(a, b); } // 不需要手动写析构函数,unique_ptr会自动释放内存 RequestHandler(const RequestHandler&) = delete; RequestHandler& operator=(const RequestHandler&) = delete; }; // main函数保持不变...
用std::unique_ptr后,RequestHandler销毁时,map里的智能指针会自动释放对应的命令对象,彻底避免内存泄漏,代码也更简洁。
三、结合工厂模式:解耦命令创建与Map填充
如果你想把命令的创建逻辑和RequestHandler解耦,同时不想用全局变量,可以用工厂模式封装命令创建:
#include <map> #include <memory> #include <stdexcept> #include <functional> #include <iostream> class ICommand { public: virtual double execute(double, double) = 0; virtual ~ICommand() = default; }; class Add: public ICommand { /* 实现不变 */ }; class Sub : public ICommand { /* 实现不变 */ }; class Mul : public ICommand { /* 实现不变 */ }; class Div : public ICommand { /* 实现不变 */ }; // 命令工厂:专门负责创建命令对象 class CommandFactory { public: static std::unique_ptr<ICommand> createCommand(int action) { switch(action) { case 1: return std::make_unique<Add>(); case 2: return std::make_unique<Sub>(); case 3: return std::make_unique<Mul>(); case 4: return std::make_unique<Div>(); default: throw std::invalid_argument("无效的操作码"); } } }; class RequestHandler { // 存储命令创建函数,延迟创建(第一次使用时才创建对象) std::map<int, std::function<std::unique_ptr<ICommand>()>> commandCreators; // 或者提前创建所有命令存入map:std::map<int, std::unique_ptr<ICommand>> commands; public: RequestHandler() { // 把工厂的创建逻辑绑定到map里 commandCreators[1] = [](){ return CommandFactory::createCommand(1); }; commandCreators[2] = [](){ return CommandFactory::createCommand(2); }; commandCreators[3] = [](){ return CommandFactory::createCommand(3); }; commandCreators[4] = [](){ return CommandFactory::createCommand(4); }; // 如果想提前创建所有命令,用下面的代码: // commands[1] = CommandFactory::createCommand(1); // commands[2] = CommandFactory::createCommand(2); // ... } double HandleRequest(int action, double a, double b) { auto it = commandCreators.find(action); if (it == commandCreators.end()) { throw std::invalid_argument("无效的操作码"); } // 延迟创建命令对象 auto command = it->second(); return command->execute(a, b); // 如果是提前创建的方式,用下面的代码: // auto it = commands.find(action); // if (it == commands.end()) ... // return it->second->execute(a,b); } }; // main函数保持不变...
这种方式的好处是:
- 命令的创建逻辑集中在工厂类里,后续新增命令只需要修改工厂,不用动
RequestHandler,解耦性更好。 - 可以选择延迟创建(节省内存,适合命令多但不常用的场景)或者提前创建(响应更快),灵活度高。
- 完全不需要全局变量,所有逻辑都封装在类里。
四、终极简化:用函数对象替代命令模式
因为你的命令类都重载了operator(),其实可以直接用std::function存储这些运算,省去接口和子类的定义,代码会非常简洁:
#include <map> #include <functional> #include <stdexcept> #include <iostream> // 直接定义运算函数 double add(double a, double b) { return a + b; } double sub(double a, double b) { return a - b; } double mul(double a, double b) { return a * b; } double div(double a, double b) { if (b == 0) throw std::invalid_argument("不能除以零"); return a / b; } class RequestHandler { // 直接存储函数对象 std::map<int, std::function<double(double, double)>> commands; public: RequestHandler() { commands[1] = add; commands[2] = sub; commands[3] = mul; commands[4] = div; // 也可以用lambda表达式直接写逻辑 // commands[1] = [](double a, double b){ return a + b; }; } double HandleRequest(int action, double a, double b) { auto it = commands.find(action); if (it == commands.end()) { throw std::invalid_argument("无效的操作码"); } return it->second(a, b); } }; int main(int argc, char* argv[]) { RequestHandler handler; try { double result = handler.HandleRequest(2, 4, 6); std::cout << "计算结果:" << result << std::endl; } catch (const std::exception& e) { std::cerr << "错误:" << e.what() << std::endl; } return 0; }
这种方式完全抛弃了命令模式的类结构,直接用函数或lambda实现运算,代码简洁易懂,适合你这种简单的二元运算场景。
内容的提问来源于stack exchange,提问作者Dmitry Sokolov
相关产品推荐
相关产品推荐

