C++ 避免向下转型或变体
C++ Avoiding down-casting or variants
一段时间以来我一直面临设计问题:
我正在将源代码字符串解析为令牌对象的一维数组。
根据令牌的类型(文字、符号、标识符),它有一些特定于令牌类型的数据。文字有值,符号有符号类型,标识符有名称。
然后我通过分析这个 1 维标记数组来构建在该源代码字符串中定义的脚本的抽象表示。语法分析逻辑是在这些令牌对象之外完成的。
我的问题是我需要将所有标记(无论它们是什么类型)存储到一个数组中,因为它似乎更容易分析,而且我看不到任何其他方法可以做到这一点。这涉及通过创建 class 层次结构:
为所有不同的令牌类型提供通用类型
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
};
class 层次结构将按如下方式使用:
// 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
}
}
}
但是它有几个问题:
基础标记class必须知道它的子classes,这似乎是错误的。
它涉及向下转换以根据令牌的类型访问特定数据,有人告诉我这也是错误的。
变体解决方案将以类似的方式使用,无需向下转换:
for( auto cur_token: tokens )
{
if( cur_token->type == E_SYMBOL )
{
switch( cur_token->symbol_type )
{
// etc
}
}
}
第二个解决方案的问题是它将所有内容混合成一个 class,这对我来说似乎不太干净,因为根据令牌的类型有未使用的变量,并且因为class 应该表示单个 "thing" 类型。
您是否有另一种可能性来建议设计这个?有人告诉我有关访问者模式的信息,但我无法想象我将如何在我的案例中使用它。
我想保留对数组进行迭代的可能性,因为我可能必须在两个方向上进行迭代,从一个随机位置开始,也许多次。
谢谢。
选项 1:"fat" 键入 一些共享/一些专用字段
为您的 "some token-type-specific data. Literals have a value, symbols have a symbol type, and identifiers have a name."
选择一组可以以令牌类型特定方式重新调整用途的数据成员
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_;
};
一个问题是共享字段的名称可能过于模糊,因此它们不会对任何给定的令牌类型产生积极的误导,而 "specific" 字段仍然可以访问并且在以下情况下可能会被滥用令牌是另一种类型。
选项2:歧视联合 - 最好为你精心包装ala boost::variant<>
:
struct Symbol { ... };
struct Identifier { ... };
struct Literal { ... };
typedef boost::variant<Symbol, Identifier, Literal> Token;
std::list<Token> tokens;
有关数据检索选项,请参阅 tutorial。
选项 3:OOD - classic 面向对象方法:
几乎是你所拥有的,但关键是 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; ...
您担心的是:
•The base token class has to know about it's subclasses, which seems wrong.
鉴于 RTTI,没有必要。
•It involves down casting to access specific data depending on the type of a token, which i was told is wrong too.
通常,在 OO 中,创建一个基础 class API 来表达整个层次结构可以实现的一组逻辑操作是实用且可取的,但在您的情况下可能需要一个 "fat" 接口(这意味着 - 很多操作 - 如果它们在 API 中 - 会混淆无操作(即什么也不做)或以某种方式(例如 return value, exceptions) 报告很多操作不支持。比如获取符号类型 classification 对于非符号没有意义。让它在 dynamic_cast
之后才可以访问会好一点而不是让它总是可访问但只是有时有意义,如 "option 1",因为在强制转换之后有编译时检查使用情况。
一段时间以来我一直面临设计问题:
我正在将源代码字符串解析为令牌对象的一维数组。
根据令牌的类型(文字、符号、标识符),它有一些特定于令牌类型的数据。文字有值,符号有符号类型,标识符有名称。
然后我通过分析这个 1 维标记数组来构建在该源代码字符串中定义的脚本的抽象表示。语法分析逻辑是在这些令牌对象之外完成的。
我的问题是我需要将所有标记(无论它们是什么类型)存储到一个数组中,因为它似乎更容易分析,而且我看不到任何其他方法可以做到这一点。这涉及通过创建 class 层次结构:
为所有不同的令牌类型提供通用类型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
};
class 层次结构将按如下方式使用:
// 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
}
}
}
但是它有几个问题:
基础标记class必须知道它的子classes,这似乎是错误的。
它涉及向下转换以根据令牌的类型访问特定数据,有人告诉我这也是错误的。
变体解决方案将以类似的方式使用,无需向下转换:
for( auto cur_token: tokens )
{
if( cur_token->type == E_SYMBOL )
{
switch( cur_token->symbol_type )
{
// etc
}
}
}
第二个解决方案的问题是它将所有内容混合成一个 class,这对我来说似乎不太干净,因为根据令牌的类型有未使用的变量,并且因为class 应该表示单个 "thing" 类型。
您是否有另一种可能性来建议设计这个?有人告诉我有关访问者模式的信息,但我无法想象我将如何在我的案例中使用它。
我想保留对数组进行迭代的可能性,因为我可能必须在两个方向上进行迭代,从一个随机位置开始,也许多次。
谢谢。
选项 1:"fat" 键入 一些共享/一些专用字段
为您的 "some token-type-specific data. Literals have a value, symbols have a symbol type, and identifiers have a name."
选择一组可以以令牌类型特定方式重新调整用途的数据成员 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_;
};
一个问题是共享字段的名称可能过于模糊,因此它们不会对任何给定的令牌类型产生积极的误导,而 "specific" 字段仍然可以访问并且在以下情况下可能会被滥用令牌是另一种类型。
选项2:歧视联合 - 最好为你精心包装ala boost::variant<>
:
struct Symbol { ... };
struct Identifier { ... };
struct Literal { ... };
typedef boost::variant<Symbol, Identifier, Literal> Token;
std::list<Token> tokens;
有关数据检索选项,请参阅 tutorial。
选项 3:OOD - classic 面向对象方法:
几乎是你所拥有的,但关键是 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; ...
您担心的是:
•The base token class has to know about it's subclasses, which seems wrong.
鉴于 RTTI,没有必要。
•It involves down casting to access specific data depending on the type of a token, which i was told is wrong too.
通常,在 OO 中,创建一个基础 class API 来表达整个层次结构可以实现的一组逻辑操作是实用且可取的,但在您的情况下可能需要一个 "fat" 接口(这意味着 - 很多操作 - 如果它们在 API 中 - 会混淆无操作(即什么也不做)或以某种方式(例如 return value, exceptions) 报告很多操作不支持。比如获取符号类型 classification 对于非符号没有意义。让它在 dynamic_cast
之后才可以访问会好一点而不是让它总是可访问但只是有时有意义,如 "option 1",因为在强制转换之后有编译时检查使用情况。