【问题标题】:C++ - Finding the proper design for thisC++ - 为此找到合适的设计
【发布时间】:2014-12-12 09:48:18
【问题描述】:

我正在编写一个脚本解释器,我首先需要标记一个包含源代码的字符串。为此,我发现了不同的东西:

  • 标识符(变量名);
  • 符号(+、-等...包括“字母”运算符,例如“return”);
  • 文字值(true、false、1、3.14、“foo”)。

为了表示这一点,我考虑了两种不同的方式:要么创建类层次结构:

class Token
{
public:
    enum type_e { E_IDENTIFIER, E_SYMBOL, E_LITTERAL }
    const type_e type;
};
class Identifier : public Token
{
public:
    const string name
}
class Symbol : public Token
{
public:
    const symbol_e symbol;
}
class Litteral : public Token
{
public:
    const Value value;
}

我会以这种方式使用向下转换:

bla bla parseStatement( bla bla )
{
    // ...
    Token * curTok = tokens[ curPos ];
    if( curTok->type == E_SYMBOL && dynamic_cast< Symbol * >( curTok )->symbol == E_PLUS )
    {
       // ...
    }
    // ...
}

但有人告诉我,向下铸造意味着我的设计可能有缺陷。而且也违反了多态性原则。

然后我想到了第二种方法,使用某种包含所有内容的变体类:

class Token
{
private:
    type_e _type;
public:
    type_e getType()
    bool isIdentifier()
    bool isSymbol()
    bool isLitteral()
    string getName() // If is an identifier, else exception
    symbol_e getSymbol() // If is a symbol, else exception
    Value getValue() // If is a litteral, else exception
}

我会用这种方式:

bla bla parseStatement( bla bla )
{
    // ...
    Token curTok = tokens[ curPos ];
    if( curTok.isSymbol() && curTok.getSymbol() == E_PLUS )
    {
       // ...
    }
    // ...
}

但在我看来,它并不干净。基本上是一样的,只是写的短了一点。

有人建议我使用访问者设计模式,但我想不出一种方法来解决我的问题。 我想将语法分析逻辑保留在标记之外。该逻辑将在一个语法分析类中,该类将操纵这些标记。 我还需要令牌具有相同的类型或具有公共基类,以便我可以将它们存储在单个数组中。

你知道我该如何设计这个吗?谢谢你:)

【问题讨论】:

  • 我更喜欢您的解决方案。为了便于阅读,您可以添加 is*as* 方法,以便您的 if 检查可以读取 curTok.isSymbol() &amp;&amp; curTok.asSymbol()-&gt;symbol == E_PLUS 然后 asSymbol 将执行 dynamic_cast&lt;Symbol*&gt;
  • 感谢您的回答!它确实会使代码更容易读/写,但它仍然依赖于向下转换,所以它只会转移问题。
  • 向下转换通常被认为是不好的,因为您试图获得比您要求的更具体的代码,但是 - 您实际上是在创建一个 run time type information 系统。
  • 我认为一个巨大的变体类更难维护和测试,但我想这与偏好有关。
  • 不是每个设计都应该是面向对象的。 pair&lt;type_e,string&gt; 就足够了。

标签: c++ design-patterns polymorphism variant downcast


【解决方案1】:

这是一个带有访客模式的版本

#include <iostream>
using namespace std;

class Value { };
class Identifier;
class Symbol;
class Literal;
class ParseVisitor {
public:
    virtual void VisitAndParseFor(Identifier& d1) = 0;
    virtual void VisitAndParseFor(Symbol& d2) = 0;
    virtual void VisitAndParseFor(Literal& d1) = 0;
};

class Token {
public:
    virtual void ParseWith(ParseVisitor& v) = 0;
};

class Identifier : public Token {
public:
    virtual void ParseWith(ParseVisitor& v) {
        v.VisitAndParseFor(*this);
    }
    const string name;
};

class Symbol : public Token {
public:
    enum symbol_e {
        E_PLUS, E_MINUS
    };
    virtual void ParseWith(ParseVisitor& v) {
        v.VisitAndParseFor(*this);
    }
    symbol_e symbol;
};

class Literal : public Token {
public:
    virtual void ParseWith(ParseVisitor& v) {
        v.VisitAndParseFor(*this); 
    }
    Value value;
};


// Implementing custom ParseVisitor
class Parser : public ParseVisitor {
    virtual void VisitAndParseFor(Identifier& identifier) { 
        std::printf("Parsing Identifier\n"); 
    }
    virtual void VisitAndParseFor(Symbol& symbol) { 
        std::printf("Parsing Symbol\n"); 
        switch (symbol.symbol) {
            case Symbol::symbol_e::E_PLUS: std::printf("Found plus symbol\n"); break;
            case Symbol::symbol_e::E_MINUS: std::printf("Found minus symbol\n"); break;
        }
    }
    virtual void VisitAndParseFor(Literal& literal) { 
        std::printf("Parsing Literal\n"); 
    }
};

int main() {
    Parser p;

    Identifier identifier;
    Symbol symbol;
    symbol.symbol = Symbol::symbol_e::E_PLUS;
    Literal literal;

    identifier.ParseWith(p);
    symbol.ParseWith(p);
    literal.ParseWith(p);

    return 0;
}

如果解析时需要上下文数据,则将参数添加到Token::ParseWithParseVisitor::VisitAndParseFor。同样,如果您需要返回状态/数据,您可以更改返回签名。

【讨论】:

  • 感谢您抽出宝贵时间回答。当我被建议使用访问者模式时,我想到了一些接近这一点的东西,但它似乎使事情变得比必要的复杂得多。例如,当我传递一个表达式以从中生成一棵树时,有可能使用它们在给定令牌数组中的位置来访问令牌感觉很舒服。我认为使用这种方式会使这变得更加复杂,除非我能找到修改它的方法。无论如何谢谢:)
  • 您可以扩展上述签名以获取令牌列表和当前索引 - 如果这是您需要的上下文(我是在您的帖子的 cmets 中支持您的原始解决方案的人 - 它取决于编译器的大小。如果你在 Token 变体上扩展很多,这个版本更容易测试和扩展)
  • 不,令牌的类型不太可能永远改变。我可能会添加新符号(例如新关键字)或新文字类型(例如字符、布尔值),但这 3 个类别可能永远不会改变。即使他们以一种太重要的方式改变,我也会重写整个事情。我会想办法修改你的想法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-12-18
  • 1970-01-01
  • 2018-03-08
  • 1970-01-01
  • 2013-01-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多