式の構文木を比較できるようにしたい。基本クラスには、具体的なサブクラスがオーバーライドするためExpr
の純粋仮想メソッドがあります。compare
class Expr {
public:
virtual bool compare(const Expr *other) const = 0;
};
例として、とはNumExpr
、AddExpr
それぞれリテラル整数式と2進加算式を表す2つの具体的なサブクラスです。各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) {}
...
};
次に、RTTIを回避するためstatic_cast
にフィールドを使用できます。flavor
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が内部で行っていることを複製しているように見えます。