在C语言中使用Facade设计模式的潜在缺点是什么?[已关闭]

vi4fp9gy  于 2023-01-20  发布在  其他
关注(0)|答案(2)|浏览(142)

已关闭。此问题为opinion-based。当前不接受答案。
**想要改进此问题吗?**请更新此问题,以便editing this post可以用事实和引文来回答。

昨天关门了。
Improve this question
作为一个充满激情的项目,我写编译器已经有一段时间了。目前,编译器完全是用C编写的。我使用访问者模式来进行类型检查、代码生成、调试打印等。随着时间的推移,我意识到,我不需要这样一个复杂的系统来表示各种AST节点。每当我想添加另一个AST节点时,它需要添加大量的样板代码和方法来“访问”该节点。
我一直在考虑用C语言重写编译器的前端,但在后端仍然使用C
LLVM-IR API来简化操作。我认为OOP可能会给AST节点的表示方式增加一些不必要的复杂性,所以我在C语言中使用了Facade模式:

typedef enum {
  AST_NUM,
  AST_EXPR
} NodeType;

typedef struct AstNumNode AstNumNode;
typedef struct AstExprNode AstExprNode;

typedef struct AstNode {
  NodeType type;
  union {
    AstNumNode* num;
    AstExprNode* expr;
  } actual;
} AstNode;

struct AstExprNode {
  AstNode* left;
  AstNode* right;
};

struct AstNumNode {
  int value;
};

当然,我仍然需要编写如下方法:

void ast_node_print(AstNode* node, int depth); // switch on node->type and select one of the below methods based on that type
void ast_num_print(AstNumNode* node, int depth);
void ast_expr_print(AstExprNode* node, int depth);

我认为它比访问者模式简单得多。
但是,当我创建这些节点时,执行以下操作可能会有点繁琐:

AstNode* numNode = malloc(sizeof(AstNode));
numNode->type = AST_NUM;
numNode->actual.num = malloc(sizeof(AstNumNode));
numNode->actual.num->value = 10;

我能看到的最后一个缺点是空间。由于联合,每个AstNode将占用相同的空间量,而不管它 Package 了什么“子节点”。当编译大型程序时,我可以看到这成为一个问题。

n3h0vuf2

n3h0vuf21#

我能看到的最后一个缺点是空间,由于联合,每个AstNode将占用相同的空间量,而不管它 Package 的是哪个“子节点
在C中,使用flexible array member,以便根据需要调整最后一个成员的内存大小。

typedef struct AstNode {
  NodeType type;
  union {
    AstNumNode num;
    AstExprNode expr;
  } actual[];
} AstNode;

...然后分配到所需的大小

AstNode* numNode;
switch (type) {
  case AST_NUM: numNode = malloc(sizeof numNode[0] + sizeof numNode->actual[0].num); 
    numNode->type = type;
    numNode->actual.num.value = 10;
    break;
  case AST_EXPR: numNode = malloc(sizeof numNode[0] + sizeof numNode->actual[0].expr);
    // Assign values
    numNode->type = type;
    numNode->actual.expr.left = NULL;
    numNode->actual.expr.right = NULL;
    break;

这将旧代码中完成的2个分配滚动到1个。
然而,在没有看到更大背景的情况下,还不清楚这是否值得。

真正的目标

OP似乎想减少复杂性/代码和对象内存空间。这些通常与地球其他冲突。决定:选择balance或选择one

9bfwbjaz

9bfwbjaz2#

我已经尝试了建议的方法。最后,随着代码的发展,联盟变得如此之大,很难保持代码的可读性。此外,还有一些样板文件来为每种节点启动正确的成员。
我也认为访问者模式是冗长的。我最后使用的是一个名为AstNode的基类,它有一个种类(你的NodeType类型)。每个扩展AstNode的类都设置了适当的种类。这被认为是不好的,因为我做了很多指针转换,但最终对我来说比访问者方法更理智。
虽然没有使用访问者模式,但我最终编写了许多类似于访问者的方法,但没有样板(相同的函数适用于许多节点,并进行了一些类型转换)。
在AstNode中有一个类也帮助我跟踪我还没有实现的用例,我可以做一个切换用例,并设置一个带有警告消息的默认值,以提醒我一个已实现的用例。
我不知道你的语言现在在哪里。我的是在中间代码生成步骤,我如何访问这里的Ast方法帮助我驱动“final”结构。例如,我想轻松访问标识符的类型、符号、字节大小等。所以我在AstIdentifier中添加了一些指针,如果我使用联合方法,它将对我的跟踪能力来说太大了。所以不用传递符号表上下文或者其他什么,我可以使用id.get_type(),id.get_size(),id.get_address()等等。
最后,我的建议是:选择对你来说比较容易的方法。2做这件事真的没有对错之分。3我花了一些时间来适应这种方法并感到舒服。
如果可以的话,请阅读Terence Parr的《语言实现模式》一书。如果我没记错的话,第4章讨论了构建AST的不同方法,以及每种方法的优缺点,包括您建议的方法。

相关问题