【发布时间】:2013-01-04 06:15:17
【问题描述】:
我希望能够比较表达式的语法树。基类Expr 有一个纯虚compare 方法供具体子类覆盖:
class Expr {
public:
virtual bool compare(const Expr *other) const = 0;
};
例如,假设NumExpr 和AddExpr 是两个具体的子类,分别表示文字整数表达式和二进制加法表达式。每个compare 方法所做的第一件事是使用dynamic_cast 来确保other 表达式属于同一类型:
class NumExpr : public Expr {
int num;
public:
NumExpr(int n) : num(n) {}
bool compare(const Expr *other) const {
const NumExpr *e = dynamic_cast<const NumExpr*>(other);
if (e == 0) return false;
return num == e->num;
}
};
class AddExpr : public Expr {
Expr *left, *right;
public:
AddExpr(Expr *l, Expr *r) : left(l), right(r) {}
bool compare(const Expr *other) const {
const AddExpr *e = dynamic_cast<const AddExpr*>(other);
if (e == 0) return false;
return left->compare(e->left) && right->compare(e->right);
}
};
我在使用dynamic_cast的时候总觉得自己做错了什么--
是否有更合适的方法来执行对象之间的动态比较
不使用dynamic_cast?
使用visitor design pattern 确实不解决了对 RTTI 的需求(据我所知)。 “表达式访问者”的抽象基类可能如下所示:
class NumExpr;
class AddExpr;
class ExprVisitor {
public:
virtual void visit(NumExpr *e) {}; // "do nothing" default
virtual void visit(AddExpr *e) {};
};
表达式的基类包含一个纯虚 accept 方法:
class Expr {
public:
virtual void accept(ExprVisitor& v) = 0;
};
具体的表达式子类然后使用 double dispatch 调用适当的visit 方法:
class NumExpr : public Expr {
public:
int num;
NumExpr(int n) : num(n) {}
virtual void accept(ExprVisitor& v) {
v.visit(this);
};
};
class AddExpr : public Expr {
public:
Expr *left, *right;
AddExpr(Expr *l, Expr *r) : left(l), right(r) {}
virtual void accept(ExprVisitor& v) {
v.visit(this);
};
};
当我们最终使用这种机制进行表达式比较时,我们仍然需要使用 RTTI(据我所知);例如,下面是一个用于比较表达式的示例访问者类:
class ExprCompareVisitor : public ExprVisitor {
Expr *expr;
bool result;
public:
ExprCompareVisitor(Expr *e) : expr(e), result(false) {}
bool getResult() const {return result;}
virtual void visit(NumExpr *e) {
NumExpr *other = dynamic_cast<NumExpr *>(expr);
result = other != 0 && other->num == e->num;
}
virtual void visit(AddExpr *e) {
AddExpr *other = dynamic_cast<AddExpr *>(expr);
if (other == 0) return;
ExprCompareVisitor vleft(other->left);
e->left->accept(vleft);
if (!vleft.getResult()) return;
ExprCompareVisitor vright(other->right);
e->right->accept(vright);
result = vright.getResult();
}
};
请注意,我们仍在使用 RTTI(dynamic_cast 是这种情况)。
如果我们真的希望避免 RTTI,我们可以“自己动手”创建独特的常量来识别每个具体的表达式风格:
enum ExprFlavor {
NUM_EXPR, ADD_EXPR
};
class Expr {
public:
const ExprFlavor flavor;
Expr(ExprFlavor f) : flavor(f) {}
...
};
每个具体类型都会适当地设置这个常量:
class NumExpr : public Expr {
public:
int num;
NumExpr(int n) : Expr(NUM_EXPR), num(n) {}
...
};
class AddExpr : public Expr {
public:
Expr *left, *right;
AddExpr(Expr *l, Expr *r) : Expr(ADD_EXPR), left(l), right(r) {}
...
};
然后我们可以使用static_cast 和flavor 字段来避免RTTI:
class ExprCompareVisitor : public ExprVisitor {
Expr *expr;
bool result;
public:
ExprCompareVisitor(Expr *e) : expr(e), result(false) {}
bool getResult() const {return result;}
virtual void visit(NumExpr *e) {
result = expr->flavor == NUM_EXPR && static_cast<NumExpr *>(expr)->num == e->num;
}
...
};
这个解决方案似乎只是在复制 RTTI 在幕后所做的事情。
【问题讨论】:
-
查找双重调度。
-
或者访客设计模式。
-
如果
underlying expressions are the same比较函数返回 true 并在其他情况下返回 false 是否正确...? -
是的。这用于通过识别表达式中的公共子树将语法树转换为语法 DAG。例如(3 + x)*2 + (3 + x)/2 只表示 3 + x 一次。
-
感谢有关使用 visitor 设计模式 和 double dispatch 的提示,因为这对编译器的语法树很有意义(如在these UW compiler course notes 中描述)。 OTOH,它没有解决原来的问题,因为
visit方法仍然需要执行某种RTTI 来确定两个表达式是否属于同一类型。我认为这是 RTTI 可以的一个很好的例子。
标签: c++