【问题标题】:C++ dynamic object class comparisonC++ 动态对象类比较
【发布时间】:2013-01-04 06:15:17
【问题描述】:

我希望能够比较表达式的语法树。基类Expr 有一个纯虚compare 方法供具体子类覆盖:

class Expr {
public:
  virtual bool compare(const Expr *other) const = 0;
};

例如,假设NumExprAddExpr 是两个具体的子类,分别表示文字整数表达式和二进制加法表达式。每个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_castflavor 字段来避免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++


【解决方案1】:

假设您在编译时不知道任何一方的动态类型(例如静态类型与动态类型相同)并且您实际上想通过指针或引用比较两个Expr对象,那么您将不得不进行两次虚拟调用(双重调度)或使用dynamic_cast

看起来像这样:

class Expr {
public:
  virtual bool compare(const Expr *other) const = 0;
  virtual bool compare(const NumExpr *other) const { return false; }
  virtual bool compare(const AddExpr *other) const {return false;}
};

class NumExpr : public Expr {
  int num;
public:
  explicit NumExpr(int n) : num(n) {}
  bool compare(const Expr *other) const {
    return other->compare(this);
  }
  bool compare(const NumExpr *other) const {
    return num == other->num;
  }
};

【讨论】:

  • 此特定解决方案要求 Expr 事先了解所有派生类型,如 NumExpr... 需要在代码中使用 forward declaration... 无法在不更改母亲代码的情况下添加新表达式Expr...
  • 这当然避免了dynamic_cast,但是当有许多子类时变得笨拙,并且首先颠覆了形成类层次结构的原因。
【解决方案2】:

可以使用typeid Operator 代替dynamic_cast 进行初始类型匹配。

(src)

【讨论】:

  • 真的是一样的方法。
【解决方案3】:

您可以使用 RTTI。

class NumExpr : public Expr {
  int num;
public:
   NumExpr(int n) : num(n) {}
bool compare(const Expr *other) const {
if ( typeid(*other) != typeid(*this) )
     return false;
 else {
     NumExpr *e = static_cast<NumExpr*>(other);
     return num == e->num;
  }
  }
};

【讨论】:

  • 什么是e?不编译,
  • 使用 typeidstatic_cast 与使用 dynamic_cast 并没有什么不同。
  • typeiddynamic_cast 之间的一个区别是dynamic_cast 可以在继承树的任何位置找到子对象,但typeid 只会告诉您派生最多的类型。所以在大多数情况下,typeid 甚至比dynamic_cast 还要糟糕。
  • 是的 aschepler,虽然实际对象是子类型,但 dynamic_cast 将成功用于基本类型。所以在上面的代码中,如果 other 是 NumExpr 的子类型,比如 NumExprSpecial,dynamic_cast (other) 将成功,并且 NumExprSpecial 的实例和 NumExpr 的实例最终可能相等。除非这对您的应用程序有意义,否则 typeid 是更好的解决方案。
猜你喜欢
  • 2010-10-17
  • 1970-01-01
  • 2011-07-09
  • 1970-01-01
  • 2023-03-11
  • 1970-01-01
  • 2023-03-16
  • 2021-12-04
相关资源
最近更新 更多