我有一个将DSL转换为C++的程序,它在中间表示上使用访问者模式。
我经常需要将当前处理的节点替换为另一种类型(例如,用类型定义替换“未解决的类型”)。
做这种事的好模式是什么?
我试过:
visit方法不返回值。这里的困难在于,这是相当容易出错的--如果我忘记在下降到树中之前存储指针,我可能会覆盖错误的引用。有没有更好的选择?
发布于 2016-03-26 12:11:33
visit()方法在C++中描述时通常是无效的,这只是一个实现细节。这绝不是访客模式的中心部分。在Gamma,Helm,Johnson,Vlisside的“Design”一书中,代码示例通常在C++和Smalltalk中提供。Smalltalk是一种非常动态的语言。该书中Smalltalk中访问者模式的示例代码实际上返回值!这是一个正则表达式的评估器。我将把代码翻译成JavaScript,以使其更容易理解。
对象结构(正则表达式)由四个类组成,它们都有一个以访问者为参数的
accept()方法。在类SequenceExpression中,accept方法是函数接受(AVisitor){返回aVisitor.visitSequence(this);} … ConcreteVisitor类是REMatchingVisitor。…其方法…返回表达式将匹配的一组流以标识当前状态。函数visitSequence(sequenceExp) { this.inputState =序列this.表达式1.接受(此);返回序列this.表达式2.接受(此);}.函数visitAlternation(alternateExp) { var originalState = this.inputState;var finalState =交替执行;this.inputState = originalState;this.inputState返回finalState;}函数visitLiteral(literalExp) { var finalState =新集合();this.inputState.foreach(函数(流){ var tStream = stream.copy();if (tStream.nextAvailable(literalExp.value.size) stream.copy)){en19#(););返回finalState;}
那么为什么没有在C++中完成呢?为什么所有的visit()方法都无效?因为虚拟accept()方法不知道返回什么。每个访问者可能返回不同的类型。虽然accept方法应该只是传递该值,但C++需要精确的类型。这可以用模板表示,但是虚拟方法不能是模板化的方法。(虚拟方法的要点是,实现只能在运行时(后期绑定)得到解析,而模板必须在编译时完全评估。由于在编译时不知道将选择哪种方法,因此不会调用模板)。
通常的解决方法是将返回值存储在访问者对象的实例字段中。这很尴尬,并且要么需要默认的可构造返回值,要么需要指针间接。虽然这在语义上等同于直接返回值,但这使得使用访问者非常尴尬,以至于我常常创建一个包装器函数来运行访问者:
class Visitor;
class Base {
public:
virtual void accept(Visitor&) const = 0;
};
class A;
class B;
class Visitor {
public:
virtual void visitA(A const&) = 0;
virtual void visitB(B const&) = 0;
};
class A { … };
class B { … };
class ConcreteVisitor : public Visitor final {
int mResult;
public:
ConcreteVisitor() : mResult(0) {}
void visitA(A const& a) override { mResult = 1; }
void visitB(B const& a) override { mResult = 2; }
int result() const { return mResult; }
};
int runConcreteVisitor(Base const& base) {
ConcreteVisitor v;
base.accept(v);
return v.result();
}每个访问者在给定的输入层次结构上有效地表现为一个(多态)函数,或者类似于扩展方法。通过将附加参数和返回值填充到成员变量中,我们可以在C++中建模任意函数签名。(要访问的元素以外的函数参数对应于访问者的构造函数参数)。
但这只是个解决办法。如果您的所有访问者都有一个有效的签名Expression visitor(Expression const&),那么您可以将所有接受方法写成
virtual Expression accept(Visitor& v) {
return v.acceptAddition(*this);
}对于AST操作,这有时就是您所需要的。但是,一旦访问者有了不同的“返回”类型,就需要复制其他类型的所有接受/访问方法(基本上是手动模板),或者必须使用访问者成员变量解决方案。在这种情况下,从一开始就使用解决办法可能是明智的。通过使用runVisitor(…)帮助程序,这会稍微减少错误的发生,因为您不必记住检索返回值,而是直接给出一个。如果您的访问者是递归的,这也意味着您应该避免在访问者中直接发生不必要的可变状态,因为每次接受/访问调用都会创建一个新的访问者。
https://softwareengineering.stackexchange.com/questions/313783
复制相似问题