C++基类指针队列存储派生对象时无法调用派生类方法问题
问题根因
你当前的代码不存在对象切片问题。对象切片仅发生在按值存储基类实例的场景(例如定义std::queue<Symbol>存储元素),派生类独有的成员会被截断;你用std::queue<Symbol*>存储基类指针,堆上的派生类对象是完整的,没有被切片。
调用派生类方法失败的核心原因:基类Symbol没有定义对应的虚函数接口,你直接通过基类指针调用仅派生类实现的非虚方法,属于非法的跨类型访问,会触发运行时异常。
你当前的类继承结构对照如下:
- 纯虚基类
SymbolOperand:public继承SymbolNumber:public继承Operand,独有方法get_val()Constant:public继承OperandVariable: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
相关产品推荐
相关产品推荐

