解释器模式的核心是用类表示文法规则、对象组合表达语法结构、递归调用interpret()求值;它不处理词法分析,只负责对已构建的语法树进行求值,强调可扩展性与节点可求值性。

解释器模式的核心不是写个 REPL
很多人一看到“解释器模式”就去实现一个带 eval() 的命令行,其实完全跑偏了。GoF 定义的解释器模式本质是:**用类来表示文法规则,用对象组合表达语言结构,通过递归调用 interpret() 方法完成求值**。它不处理词法分析、也不管输入怎么切分,只聚焦于“如何把已解析好的语法树节点转成结果”。实际项目里极少从零手写完整解释器,但理解它的结构对设计配置表达式、规则引擎、简单 DSL 很有用。
必须手动建模终结符和非终结符类
比如想支持形如 "x + 3 * y" 的算术表达式,得先定义好语法单元对应的类,不能靠字符串拼接或 switch 分支硬编码逻辑:
-
NumberExpression表示数字字面量(终结符),interpret()直接返回value -
VariableExpression表示变量(终结符),interpret()查context哈希表取值 -
AddExpression和MultiplyExpression是非终结符,持两个Expression*子节点,interpret()分别调用左右子节点再做加/乘
漏掉任一类,或者让某个 interpret() 返回 void,整个模式就失效——它依赖每个节点都可求值并向上透传结果。
Context 传参必须是只读引用,且生命周期要兜住整个解释过程
变量查找需要上下文,但 C++ 里容易犯两个错:
- 把
Context作为值传给interpret(),导致每次递归都拷贝哈希表,性能崩盘 - 用局部
std::map构造Context,但语法树节点生命周期更长,后续interpret()访问时迭代器失效或指针悬空
正确做法是传 const Context&,且确保 Context 对象在整棵树求值期间一直有效。例如:
struct Context {
std::unordered_map<:string int> values;
};
<p>int result = expr->interpret(context); // context 必须比 expr 活得久
</p></:string>
不要试图用模板或宏绕过虚函数调用
有人想用 std::variant + std::visit 替代继承体系,或者用宏生成一堆 if (type == ADD) ... 分支。这看似省事,实则破坏了解释器模式的关键价值:**开放扩展性**。新增一种运算符(比如幂运算),你得改所有分支判断;而标准解释器只需加一个 PowerExpression 类,其他代码零修改。C++ 虚函数开销微乎其微,为这点性能牺牲可维护性不值得。真正卡性能的地方从来不是 interpret() 的虚调用,而是反复构造临时对象或低效的上下文查找。
复杂点在于语法树构建阶段——谁负责把字符串转成对象树?那已经不属于解释器模式范畴了,那是 parser 的事。模式本身只管“树建好了之后怎么算”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











