c++避免向下转换或变体

C++ Avoiding down-casting or variants

本文关键字:转换 c++      更新时间:2023-10-16

一段时间以来我一直面临着一个设计问题:

  • 我正在将源代码字符串解析为令牌对象的一维数组。

  • 根据令牌的类型(文字、符号、标识符),它有一些特定于令牌类型的数据。文字有一个值,符号有一个符号类型,标识符有一个名称。

  • 然后,我通过分析这个1维令牌数组来构建在源代码字符串中定义的脚本的抽象表示。语法分析逻辑在这些令牌对象之外完成。

我的问题是,我需要我所有的令牌,不管他们的类型,存储到一个单一的数组,因为它似乎更容易分析,因为我没有看到任何其他的方法来做到这一点。这涉及到通过创建类层次结构为所有不同的令牌类型提供一个公共类型:

class token { token_type type; };
class identifier : public token { string name; };
class litteral : public token { value val; };
class symbol : public token( symbol_type sym; };

…或者创建一个变体:

class token
{
   token_type type;
   string name; // Only used when it is an identifier
   value val; // Only used when it is a litteral
   symbol_type sym; // Only used when it is a symbol
};

类层次结构将按如下方式使用:

// Iterator over token array
for( auto cur_tok : tokens )
{
    // Do token-type-specific things depending on its type
    if( cur_token->type == E_SYMBOL )
    {
        switch( ((symbol *) cur_token)->symbol_type )
        {
            // etc
        }
    }
}

但是它有几个问题:

  • 基令牌类必须知道它的子类,这似乎是错误的。

  • 它涉及到根据令牌的类型来访问特定的数据,这也被告知是错误的

变体解决方案将以类似的方式使用,不需要向下转换:

for( auto cur_token: tokens )
{
    if( cur_token->type == E_SYMBOL )
    {
        switch( cur_token->symbol_type )
        {
            // etc
        }
    }
}

第二个解决方案的问题是,它将所有东西混合到一个类中,这对我来说似乎不是很干净,因为根据令牌的类型有未使用的变量,而且因为一个类应该表示一个单一的"东西"类型。

你有其他可能建议设计这个吗?我被告知访问者模式,但我无法想象我将如何在我的案例中使用它。

我想保留在数组上迭代的可能性,因为我可能需要在两个方向上迭代,从随机位置开始,可能需要多次。

谢谢。

选项1:"fat" type 带有一些共享/专用字段

选择一组数据成员,这些成员可以以特定于令牌类型的方式为某些特定于令牌类型的数据重新利用。字面量有一个值,符号有一个符号类型,标识符有一个名称。"

 struct Token
 {
    enum Type { Literal, Symbol, Identifier } type_;
    // fields repurposed per Token-Type
    std::string s_;  // text of literal or symbol or identifier
    // fields specific to one Token-Type
    enum Symbol_Id { A, B, C } symbol_id_;
 };

这样做的一个问题是,共享字段的名称可能过于模糊,因此它们不会对任何给定的令牌类型产生误导,而当令牌是另一种类型时,"特定"字段仍然是可访问的,并且容易被滥用。

选项2:区分联合 -最好为您打包为boost::variant<>:

struct Symbol { ... };
struct Identifier { ... };
struct Literal { ... };
typedef boost::variant<Symbol, Identifier, Literal> Token;
std::list<Token> tokens;

有关数据检索选项,请参阅教程。

选项3:OOD -经典的面向对象方法:

几乎是你所拥有的,但关键是Token类型需要一个虚析构函数。

struct Token { virtual ~Token(); };
struct Identifier : Token { string name; };
struct Literal : Token { value val; };
struct Symbol : Token { symbol_type sym; };
std::vector<std::unique_ptr<Token>> tokens_;
tokens_.emplace_back(new Identifier { the_name });

你不需要一个"type"字段,因为你可以使用c++的RTTI来检查一个特定的Token*是否指向一个特定的派生类型:

if (Literal* p = dynamic_cast<Literal>(tokens_[0].get()))
    ...it's a literal, can use p->val; ...

你的关注点是:

•基令牌类必须知道它的子类,这似乎是错误的。

没有必要考虑RTTI。

•它涉及到根据令牌的类型来访问特定的数据,这也被告知是错误的。

通常是这样,在面向对象中,创建一个表达整个层次结构可以实现的一组逻辑操作的基类API是实际和可取的,但在您的情况下,可能需要一个"胖"接口(这意味着许多操作-如果它们在API中-将会令人困惑-无操作(即什么都不做)或以某种方式(例如返回值,异常)报告许多操作不受支持。例如,获取符号类型分类对于非符号来说是没有意义的。使其仅在dynamic_cast之后可访问比始终可访问要好一些,但只是有时有意义,如在"选项1"中,因为在强制转换之后有编译时检查使用情况。