访客模式。 void *是否是完全抽象接口的可接受返回类型?

时间:2014-03-15 21:50:17

标签: c++ theory

我有一个AST,以通常的方式表示(抽象类型的节点树)。我有几个遍历这个树的用例(一个优化器,它返回另一个AST; IR代码生成,返回一个llvm::Value*;和一个调试分析器,它只输出到stdout并且不返回任何内容。)

访问者感觉这是正确的方式,但访问者的每个用例的不同返回类型使得很难看到如何为此实现接口。我考虑过这个:

class Visitor;

class ASTNode {
public:
  virtual void accept(Visitor *visitor);
};

class Visitor {
public:
  virtual void visit(CallNode *node) = 0;
  virtual void visit(BinExprNode *node) = 0;
  // etc
};

由于缺少返回值,每个Visitor实现都需要构建内部状态并提供具有合适返回类型的result()方法。然而,这很复杂,因为每次调用visit()都需要围绕被访问表达式的某些上下文(即,单独调用,或者它是否被用作二进制表达式的一部分?)。对于像二进制表达式代码生成这样的事情来说,通过访问操作数的节点来收集返回值(我可以使用内部临时状态变量或其他类似的东西,但它感觉过度设计)并且难以推理(国家不断变化。)

class OptimizingVisitor : public Visitor {
  ASTNode *m_result;
public:
  ASTNode *result() {
    return m_result;
  }

  void setResult(ASTNode *node) {
    m_result = node;
  }

  void visit(CallNode *node) {
    node->left()->accept(this);
    ASTNode *optimizedL = result();
    node->right()->accept(this);
    ASTNode *optimizedR = result();
    setResult(new CallNode(optimizedL, optimizedR));
  }

  // ... snip ...
};

我可以通过为每个节点传入一个新的访问者来避​​免“状态不断变化”的问题变得更加复杂。只是觉得这个问题需要一个更实用的解决方案。

真的,我想更简单,更容易阅读以上内容:

class OptimizingVisitor : public Visitor {
public:
  ASTNode* visit(CallNode *node) {
    ASTNode *optimizedL = node->left()->accept(this);
    ASTNode *optimizedR = node->right()->accept(this);
    return new CallNode(optimizedL, optimizedR);
  }

  // ... snip ...
};

当然,现在我已经更改了返回类型,因此它不适合我的访问者合同。除了为每个可能的访问者实现定义完全独立的接口并使AST知道这些访问者类型之外,似乎唯一合乎逻辑的用法是void*。这种API是否保证使用void*?有更好的方法吗?

2 个答案:

答案 0 :(得分:4)

如果我做对了,我可能会有一些有用的东西。在https://gist.github.com/d11wtq/9575063参考您的样本,您不能拥有

class ASTNode {
public:
   template <class T>
   virtual T accept(Visitor<T> *visitor);
};

因为没有模板虚函数。但是,您可能有一个通用类

template <class D, class T>
struct host {
   virtual T accept(Visitor<T> * visitor);
   // I guess, { return visitor->visit(static_cast <D*>(this)); }
};

和一个集合

template <class D, class... T>
struct hosts : host <D, T>... { };

这样,当可能的返回类型集合有限时,您可以说例如。

class ASTNode : public hosts <ASTNode, T1, T2, T3> {
public:
   // ...
};

现在您有三种不同的返回类型T1,T2,T3的访问者合约。

答案 1 :(得分:3)

也许这很有用。

使用返回类型协方差使accept()调用类型安全:

class Visitor;

class ASTNode
{
public:
    virtual ASTNode* accept(Visitor* visitor);
};

class CallNode;
class BinaryExpressionNode;

class Visitor
{
public:
    virtual CallNode* visit(CallNode* node) = 0;
    virtual BinaryExpressionNode* visit(BinaryExpressionNode* node) = 0;
};

实现:

class CallNode : public ASTNode
{
public:
    CallNode(BinaryExpressionNode* left, BinaryExpressionNode* right);

    // return type covariance
    CallNode* accept(Visitor* visitor) override
    {
        return visitor->visit(this);
    }

    BinaryExpressionNode* left();
    BinaryExpressionNode* right();
};

class BinaryExpressionNode : public ASTNode
{
public:
    BinaryExpressionNode* accept(Visitor* visitor) override
    {
        return visitor->visit(this);
    }
}

class OptimizingVisitor : public Visitor
{
public:
    CallNode* visit(CallNode* node) override
    {
        // return type covariance will make this operation type-safe:
        BinaryExpressionNode* left = node->left()->accept(this);
        auto right = node->right()->accept(this);
        return new CallNode(left, right);
    }

    BinaryExpressionNode* visit(BinaryExpressionNode* node) override
    {
        return new BinaryExpressionNode(node);
    }
};