出于好奇,我正在编写一个编程语言文本解析器。假设我想将标记的不可变(在运行时)图形定义为顶点/节点。这些自然是不同类型的 - 一些标记是关键字,一些是标识符等。但是它们都共享共同特征,其中图形中的每个标记指向另一个标记。此属性允许解析器知道特定标记后面的内容 - 因此图形定义了语言的正式语法。我的问题是几年前我每天都停止使用C ++,并且从那时起使用了很多更高级的语言,而且我的头部在堆分配,堆栈分配等方面完全分散。唉,我的C ++生锈了。
不过,我想立刻爬上陡峭的山坡,为自己设定以最高效的方式用这种命令式语言定义这个图形的目标。例如,我想避免使用'new'在堆上单独分配每个令牌对象,因为我认为如果我将这些令牌的整个图形背靠背地分配(以线性方式像数组中的元素一样),根据参考原理的每个位置,这将有利于性能 - 我的意思是当整个图形被压缩以沿着内存中的“线”占据最小空间,而不是将所有其令牌对象放在随机位置时,这是一个加号?无论如何,就像你看到的,这是一个非常开放的问题。
class token
{
}
class word: token
{
const char* chars;
word(const char* s): chars(s)
{
}
}
class ident: token
{
/// haven't thought about these details yet
}
template<int N> class composite_token: token
{
token tokens[N];
}
class graph
{
token* p_root_token;
}
当前的问题是:创建此图形对象的过程是什么?它是不可变的,它的思想结构在编译时是已知的,这就是为什么我可以并且想要避免按值复制东西等等 - 应该可以用文字组成这个图形吗?我希望我在这里有意义......(这不是我第一次没有。)解析器在运行时将使用该图作为编译器的一部分。仅仅因为这是C ++,我也会对C解决方案感到满意。非常感谢你提前。
答案 0 :(得分:3)
我的C ++也很生疏,所以我可能不知道最好的解决方案。但是,因为没有其他人走上前来......
你是对的,在一个区块中分配所有节点会给你最好的位置。但是,如果在程序启动时动态分配图形,那么堆分配也可能会紧密聚集在一起。
要在单个内存块中分配所有节点,我想到了两种可能性:创建并填充Vector&lt;&gt;在启动时(缺点是现在你在内存中有两次图形信息),或者使用静态数组初始化程序“Node [] graph = {...};”
对于任何一种方法,最大的障碍是您想要创建异质对象的图形。一个明显的解决方案是“不要”:您可以使您的节点成为所有可能字段的超集,并使用显式“类型”成员区分类型。
如果要保留各种节点类,则必须使用多个数组/向量:每种类型一个。
无论哪种方式,节点之间的连接必须首先根据数组索引定义(Node [3]后跟Node [10])。为了获得更好的解析性能,您可以在程序启动时根据这些索引创建直接对象指针。
我不会将文字字符串放入任何节点(在您的情况下为“word”):关键字,标识符和其他词汇元素的识别应该在与解析器分开的词法模块中完成。我认为如果你根据程序的输入区分Lexer生成的标记和程序用来解析输入的语法图节点,它也会有所帮助。
我希望这会有所帮助。
答案 1 :(得分:3)
我不知道你将如何定义一个定义任何实用编程语言语法的标记“图形”,特别是如果标记之间的关系是“允许遵循”。
表示编程语言语法的常用方法是使用Backus-Naur Form (BNF)或称为“EBNF”的扩展版本。
如果你想代表一个EBNF(“作为一个不可变图”),这个SO答案讨论了如何在C#中做到这一点。这些想法在C ++中有直接的类比。
坏消息是,大多数解析引擎无法直接使用EBNF,因为它在实践中效率太低。使用语法规则的直接表示很难构建有效的解析器;这就是人们发明解析器生成器的原因。因此,除非你打算编写一个解析器生成器,否则将这些规则放入内存结构中的必要性,更不用说“高效”了,不清楚。
最后,即使你以某种方式最佳地打包语法信息,它实际上也可能不会产生一点差异。解析器的大部分时间花在将字符分组为lexemes,有时甚至只是做空白抑制。
答案 2 :(得分:1)
我不认为令牌的许多小分配将成为瓶颈,如果确实如此,你总是可以选择一个内存池。
解决问题;因为所有令牌都有类似的数据(指向下一个,并且可能是我们正在处理的令牌的枚举值),你可以将类似的数据放在一个std :: vector中。这将是内存中的连续数据,并且非常有效地循环。
循环时,您可以检索所需的信息类型。我敢打赌,令牌本身理想情况下只包含“动作”(成员函数),例如:如果上一个和下一个令牌是数字,而我是加号,我们应该将这些数字加在一起。
因此,数据存储在一个中心位置,令牌被分配(但实际上可能不包含太多数据)并处理中心位置的数据。这实际上是一种面向数据的设计。
矢量看起来像:
struct TokenData
{
token *previous, *current, *next;
token_id id; // some enum?
... // more data that is similar
}
std::vector<TokenData> token_data;
class token
{
std::vector<TokenData> *token_data;
size_t index;
TokenData &data()
{
return (*token_data)[index];
}
const TokenData &data() const
{
return (*token_data)[index];
}
}
// class plus_sign: token
// if (data().previous->data().id == NUMBER && data().next->data().id == NUMBER)
for (size_t i = 0; i < token_data.size(); i++)
{
token_data[i].current->do_work();
}
这是一个想法。