You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

命令模式下填充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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:30:14