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

C++基类指针队列存储派生对象时无法调用派生类方法问题

问题根因

你当前的代码不存在对象切片问题。对象切片仅发生在按值存储基类实例的场景(例如定义std::queue<Symbol>存储元素),派生类独有的成员会被截断;你用std::queue<Symbol*>存储基类指针,堆上的派生类对象是完整的,没有被切片。

调用派生类方法失败的核心原因:基类Symbol没有定义对应的虚函数接口,你直接通过基类指针调用仅派生类实现的非虚方法,属于非法的跨类型访问,会触发运行时异常。

你当前的类继承结构对照如下:

  • 纯虚基类Symbol
    • Operand:public继承Symbol
      • Number:public继承Operand,独有方法get_val()
      • Constant:public继承Operand
      • Variable:public继承Operand
    • Function:public继承Symbol,独有方法return_type()
修复方案

方案1:基类定义统一虚接口(推荐,符合多态设计原则)

在基类中把所有需要对外暴露的方法声明为虚函数,派生类重写对应实现,之后就可以直接通过基类指针调用,不需要额外类型判断。
首先修正基类定义,必须添加虚析构函数,否则派生类对象析构时会内存泄漏:

class Symbol {
public:
    // 通用类型接口,所有派生类必须实现
    virtual std::string return_type() = 0;
    // 数值获取接口,非数值类型(比如Function)可以直接抛出异常
    virtual int get_val() {
        throw std::runtime_error("Current symbol is not a numeric operand");
    }
    // 基类虚析构函数,必加
    virtual ~Symbol() = default;
};

派生类重写对应接口即可,以Number和Function为例:

class Number : public Operand {
    int val;
public:
    Number(int v) : val(v) {}
    // 重写基类虚方法
    int get_val() override {
        return val;
    }
    std::string return_type() override {
        return "Number";
    }
};

class Function : public Symbol {
    std::string name;
public:
    Function(std::string n) : name(n) {}
    std::string return_type() override {
        return "Function";
    }
};

修改后你原有parse函数的逻辑不需要调整,直接调用方法即可正常运行。

方案2:显式运行时类型转换(不推荐,维护成本高)

如果不想修改基类接口,可以在调用派生类方法前,先确认指针指向的实际类型,再做转换调用。
你当前parse函数内的逻辑可以直接优化,因为创建对象的时候已经明确知道类型,不需要再从队列取front元素调用:

if( '0' <= expr[0] && expr[0] <= '9'){
    auto* num = new Number(std::stoi(expr));
    Q.push(num);
    // 直接用明确类型的指针调用,不需要跨类型访问
    std::clog << num->get_val() << "\n";
}
else if(checkif_function(expr)){
    auto* func = new Function(expr);
    Q.push(func);
    std::clog << func->return_type() << "\n";
}
else if(checkif_const(expr)){
    Q.push(new Const(expr));
}

如果是在parse函数之外处理队列元素,需要用dynamic_cast做运行时类型检查:

Symbol* sym = Q.front();
Q.pop();
if (auto* num = dynamic_cast<Number*>(sym)) {
    // 类型转换成功,是Number实例
    std::cout << num->get_val() << std::endl;
} else if (auto* func = dynamic_cast<Function*>(sym)) {
    // 类型转换成功,是Function实例
    std::cout << func->return_type() << std::endl;
}

这种方案每次调用派生类方法都要加类型判断,代码冗余度高,后续新增派生类时需要修改所有类型判断的分支,仅适合临时快速修复使用。

优化建议

当前代码用原始指针管理堆对象,很容易出现内存泄漏,建议替换为智能指针自动管理生命周期:

  • 将队列类型改为std::queue<std::unique_ptr<Symbol>>
  • 入队时使用std::make_unique创建对象,例如Q.push(std::make_unique<Number>(std::stoi(expr)))
    不需要手动调用delete,智能指针会在元素出队时自动调用正确的析构函数释放内存。

内容的提问来源于stack exchange,提问作者Kombajn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 21:21:24