我希望能够比较表达式的语法树。基类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
?
使用访问者设计模式并不能解决对 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;
};
然后,具体的表达式子类使用双重调度来调用适当的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 在幕后所做的事情。