访问者设计模式实现解释器表达式树遇循环依赖如何解决?
问题根因
你遇到的是C++的循环类型依赖问题:
Visitor<R>模板类的visitBinaryExpr方法需要识别Binary类型Binary类继承自Expr基类,需要先有Expr的完整定义Expr基类的accept方法需要先有Visitor的定义
三者的声明顺序形成了死锁,同时你原代码使用值类型传递类参数,要求编译器必须提前看到类的完整定义,进一步加剧了依赖冲突。
修复步骤
1. 补充前置声明 + 调整参数为引用/指针类型
C++中声明类的引用、指针参数,仅需要类的前置声明即可,不需要完整定义,先在文件头部补充前置声明:
#include "Token.h" #include <memory> // 前置声明,仅告知编译器类型存在,不需要完整定义 class Expr; class Binary;
2. 调整Visitor定义,修改参数为引用类型
template <typename R> class Visitor { public: virtual ~Visitor() = default; // 补充虚析构,避免内存泄漏 // 参数改为const引用,仅需要Binary的前置声明即可编译 virtual R visitBinaryExpr(const Binary& expr) = 0; // 后续新增其他表达式类型的visit方法,提前加对应前置声明即可 };
3. 定义Expr基类
此时Visitor已经完成定义,可以正常声明accept方法:
class Expr { public: virtual ~Expr() = default; // 基类必须加虚析构 template<typename R> R accept(Visitor<R>& visitor) const; // Visitor用引用传递,保证多态有效 };
4. 定义Binary派生类
此时Expr已经完成定义,可以正常继承:
class Binary : public Expr { public: // 左右操作数改为智能指针,避免基类值类型的对象切片问题 Binary(std::unique_ptr<Expr> left, Token oper, std::unique_ptr<Expr> right) : left(std::move(left)), oper(oper), right(std::move(right)) {} template<typename T> T accept(Visitor<T>& visitor) const override { return visitor.visitBinaryExpr(*this); } std::unique_ptr<const Expr> left; const Token oper; std::unique_ptr<const Expr> right; };
5. 补充类外的模板函数实现(全头文件实现场景)
如果你的模板方法需要放在头文件中,在所有类定义完成后补充实现即可:
template<typename R> R Expr::accept(Visitor<R>& visitor) const { // 动态分发到实际派生类的accept实现 if (const auto* binary = dynamic_cast<const Binary*>(this)) { return binary->accept(visitor); } // 后续新增其他派生类后,补充对应的分支判断即可 throw std::runtime_error("未识别的表达式类型"); }
额外注意点
- 所有表达式派生类的成员不要用基类
Expr的值类型,会直接切掉派生类的独有信息,必须用指针或者引用 - 访问者模式的accept、visit方法都建议加const限定,避免意外修改表达式树内容
- 如果新增其他表达式类型,只要先加对应前置声明,再在Visitor中加对应的visit方法即可
内容的提问来源于stack exchange,提问作者diego_rod
相关产品推荐
相关产品推荐

