使用模板实现树形结构遍历的访问者模式报错问题
问题原因
C++标准明确禁止虚函数为函数模板。这是因为虚函数的调用依赖编译阶段生成的虚函数表(vtable),而模板函数仅在实例化时才生成具体的函数实现。编译器无法提前预知所有可能的模板参数T,也就无法将所有对应的accept模板实例预载入虚表,因此代码中Expr::accept的写法违反了语言规则,触发编译错误。
解决方案
我们可以调整设计,移除虚模板函数,改用动态类型转换+模板方法的方式实现多态调用,同时保留返回任意类型的能力。以下是修改后的完整代码:
#include <iostream> #include <stdexcept> #define print(x) std::cout << x << "\n"; class Add; class Number; // 模板化的访问者基类 template <class T> class Visitor { public: virtual T visitAdd(Add* add) = 0; virtual T visitNumber(Number* num) = 0; }; // 加法访问者实现 template <class T> class Adder : public Visitor<T> { public: T visitAdd(Add* add) override; T visitNumber(Number* num) override; }; // 表达式基类:移除虚模板函数,改用模板方法+动态转换 class Expr { public: template <class T> T accept(Visitor<T>* visitor) { if (auto* add_node = dynamic_cast<Add*>(this)) { return add_node->accept(visitor); } if (auto* num_node = dynamic_cast<Number*>(this)) { return num_node->accept(visitor); } throw std::invalid_argument("Unknown expression type"); } virtual ~Expr() = default; // 增加虚析构,避免内存泄漏 }; class Add : public Expr { public: Expr* left; Expr* right; Add(Expr* left, Expr* right) : left(left), right(right) {} template <class T> T accept(Visitor<T>* visitor) { return visitor->visitAdd(this); } ~Add() override { delete left; delete right; } }; class Number : public Expr { public: int value; Number(int value) : value(value) {} template <class T> T accept(Visitor<T>* visitor) { return visitor->visitNumber(this); } }; template <class T> T Adder<T>::visitAdd(Add* add) { return add->left->accept(this) + add->right->accept(this); } template <class T> T Adder<T>::visitNumber(Number* num) { return static_cast<T>(num->value); // 显式转换,确保类型兼容 } int main() { // 表达式:(5 + 2) + (7 + (4 + 2)) Expr* expression = new Add( new Add(new Number(5), new Number(2)), new Add(new Number(7), new Add(new Number(4), new Number(2))) ); // 计算整数和 Adder<int> intAdder; print(expression->accept(&intAdder)); // 计算浮点数和 Adder<float> floatAdder; print(expression->accept(&floatAdder)); delete expression; // 释放内存 return 0; }
修改说明
- 移除虚模板函数:删除
Expr中virtual template形式的accept声明,改用非虚模板方法,通过dynamic_cast判断实际节点类型,调用对应子类的accept模板函数。 - 增加虚析构函数:在
Expr基类中添加虚析构,确保子类对象能被正确销毁,避免内存泄漏。 - 显式类型转换:在
Adder<T>::visitNumber中添加static_cast<T>,确保整数value能安全转换为目标类型(如float)。 - 内存管理:在
Add类的析构函数中递归删除子节点,main中最终删除根节点,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Jacob
相关产品推荐
相关产品推荐

