在 C++ 中优雅地比较多态树
Elegantly comparing polymorphic trees in C++
我有一棵多态对象树。我需要遍历两棵树并比较节点。如果节点具有不同的类型,则它们不相等。考虑这个层次结构:
struct Visitor;
struct Base {
virtual ~Base() = default;
virtual void accept(Visitor &) = 0;
};
using BasePtr = std::unique_ptr<Base>;
struct A final : Base {
void accept(Visitor &) override;
int data;
};
struct B final : Base {
void accept(Visitor &) override;
BasePtr child;
};
struct C final : Base {
void accept(Visitor &) override;
std::vector<BasePtr> children;
};
struct Visitor {
virtual void visit(const A &) = 0;
virtual void visit(const B &) = 0;
virtual void visit(const C &) = 0;
};
我知道如何实现这些功能:
bool equalNode(const A &, const A &);
bool equalNode(const B &, const B &);
bool equalNode(const C &, const C &);
我在问我应该如何实现这个功能:
bool equalTree(const Base *, const Base *);
如何使用访问者模式优雅地从 equalTree
转到 equalNode
?
类似
struct RhsVisitor : public Visitor
{
bool result;
};
struct AEqualVisitor : public RhsVisitor
{
void visit(const A & rhs) override { result = equalNode(lhs, rhs); }
void visit(const B &) override { result = false; }
void visit(const C &) override { result = false; }
const A & lhs;
};
B
和 C
类似
struct LhsVisitor : public Visitor
{
void visit(const A & a) override { rhsVisitor = std::make_unique<AEqualVisitor>(a); }
void visit(const B & b) override { rhsVisitor = std::make_unique<BEqualVisitor>(b); }
void visit(const C & c) override { rhsVisitor = std::make_unique<CEqualVisitor>(c); }
std::unique_ptr<RhsVisitor> rhsVisitor;
};
bool equalTree(const Base * lhs, const Base * rhs)
{
LhsVisitor vis;
lhs->accept(vis);
rhs->accept(*vis.rhsVisitor);
return vis.rhsVisitor->result;
};
这里有三种方法。
首先是 - 您需要进行双重分派。一侧有一名访客,另一侧有一名访客。选择一侧后,您 "save" 将该类型作为模板参数,您可以使用它来访问另一侧:
template <typename T>
struct Right : Visitor {
Right(T const* lhs) : lhs(lhs) { }
T const* lhs;
bool result;
void visit(A const& x) override { visit_impl(x); }
void visit(B const& x) override { visit_impl(x); }
void visit(C const& x) override { visit_impl(x); }
void visit_impl(T const& x) { result = equalNode(*lhs, x); }
template <typename U> void visit_impl(U const&) { result = false; }
};
struct Left : Visitor {
Left(Base const* lhs, Base const* rhs) : rhs(rhs) {
lhs->accept(*this);
}
Base const* rhs;
bool result;
void visit(A const& x) override { visit_impl(x); }
void visit(B const& x) override { visit_impl(x); }
void visit(C const& x) override { visit_impl(x); }
template <typename U>
void visit_impl(U const& lhs) {
Right<U> right(&lhs);
rhs->accept(right);
result = right.result;
}
};
bool equalTree(const Base *lhs, const Base *rhs) {
return Left(lhs, rhs).result;
}
第二个是——你作弊。你只关心两侧是同一类型的情况,所以你只需要单次调度:
struct Eq : Vistor {
Eq(Base const* lhs, Base const* rhs) : rhs(rhs) {
lhs->accept(*this);
}
Base const* rhs;
bool result;
void visit(A const& x) override { visit_impl(x); }
void visit(B const& x) override { visit_impl(x); }
void visit(C const& x) override { visit_impl(x); }
template <typename U>
void visit_impl(U const& x) {
result = equalNode(x, *static_cast<U const*>(rhs));
}
};
bool equalTree(Base const* lhs, Base const* rhs) {
if (typeid(*lhs) == typeid(*rhs)) {
return Eq(lhs, rhs).result;
} else {
return false;
}
}
第三个是 - 您不通过 OO 执行此操作,而是使用 variant
:
using Element = std::variant<A, B, C>;
struct Eq {
template <typename T>
bool operator()(T const& lhs, T const& rhs) const {
return equalNode(lhs, rhs);
}
template <typename T, typename U>
bool operator()(T const&, U const&) const {
return false;
}
};
bool equalTree(Element const& lhs, Element const& rhs) {
return std::visit(Eq{}, lhs, rhs);
}
我有一棵多态对象树。我需要遍历两棵树并比较节点。如果节点具有不同的类型,则它们不相等。考虑这个层次结构:
struct Visitor;
struct Base {
virtual ~Base() = default;
virtual void accept(Visitor &) = 0;
};
using BasePtr = std::unique_ptr<Base>;
struct A final : Base {
void accept(Visitor &) override;
int data;
};
struct B final : Base {
void accept(Visitor &) override;
BasePtr child;
};
struct C final : Base {
void accept(Visitor &) override;
std::vector<BasePtr> children;
};
struct Visitor {
virtual void visit(const A &) = 0;
virtual void visit(const B &) = 0;
virtual void visit(const C &) = 0;
};
我知道如何实现这些功能:
bool equalNode(const A &, const A &);
bool equalNode(const B &, const B &);
bool equalNode(const C &, const C &);
我在问我应该如何实现这个功能:
bool equalTree(const Base *, const Base *);
如何使用访问者模式优雅地从 equalTree
转到 equalNode
?
类似
struct RhsVisitor : public Visitor
{
bool result;
};
struct AEqualVisitor : public RhsVisitor
{
void visit(const A & rhs) override { result = equalNode(lhs, rhs); }
void visit(const B &) override { result = false; }
void visit(const C &) override { result = false; }
const A & lhs;
};
B
和 C
struct LhsVisitor : public Visitor
{
void visit(const A & a) override { rhsVisitor = std::make_unique<AEqualVisitor>(a); }
void visit(const B & b) override { rhsVisitor = std::make_unique<BEqualVisitor>(b); }
void visit(const C & c) override { rhsVisitor = std::make_unique<CEqualVisitor>(c); }
std::unique_ptr<RhsVisitor> rhsVisitor;
};
bool equalTree(const Base * lhs, const Base * rhs)
{
LhsVisitor vis;
lhs->accept(vis);
rhs->accept(*vis.rhsVisitor);
return vis.rhsVisitor->result;
};
这里有三种方法。
首先是 - 您需要进行双重分派。一侧有一名访客,另一侧有一名访客。选择一侧后,您 "save" 将该类型作为模板参数,您可以使用它来访问另一侧:
template <typename T>
struct Right : Visitor {
Right(T const* lhs) : lhs(lhs) { }
T const* lhs;
bool result;
void visit(A const& x) override { visit_impl(x); }
void visit(B const& x) override { visit_impl(x); }
void visit(C const& x) override { visit_impl(x); }
void visit_impl(T const& x) { result = equalNode(*lhs, x); }
template <typename U> void visit_impl(U const&) { result = false; }
};
struct Left : Visitor {
Left(Base const* lhs, Base const* rhs) : rhs(rhs) {
lhs->accept(*this);
}
Base const* rhs;
bool result;
void visit(A const& x) override { visit_impl(x); }
void visit(B const& x) override { visit_impl(x); }
void visit(C const& x) override { visit_impl(x); }
template <typename U>
void visit_impl(U const& lhs) {
Right<U> right(&lhs);
rhs->accept(right);
result = right.result;
}
};
bool equalTree(const Base *lhs, const Base *rhs) {
return Left(lhs, rhs).result;
}
第二个是——你作弊。你只关心两侧是同一类型的情况,所以你只需要单次调度:
struct Eq : Vistor {
Eq(Base const* lhs, Base const* rhs) : rhs(rhs) {
lhs->accept(*this);
}
Base const* rhs;
bool result;
void visit(A const& x) override { visit_impl(x); }
void visit(B const& x) override { visit_impl(x); }
void visit(C const& x) override { visit_impl(x); }
template <typename U>
void visit_impl(U const& x) {
result = equalNode(x, *static_cast<U const*>(rhs));
}
};
bool equalTree(Base const* lhs, Base const* rhs) {
if (typeid(*lhs) == typeid(*rhs)) {
return Eq(lhs, rhs).result;
} else {
return false;
}
}
第三个是 - 您不通过 OO 执行此操作,而是使用 variant
:
using Element = std::variant<A, B, C>;
struct Eq {
template <typename T>
bool operator()(T const& lhs, T const& rhs) const {
return equalNode(lhs, rhs);
}
template <typename T, typename U>
bool operator()(T const&, U const&) const {
return false;
}
};
bool equalTree(Element const& lhs, Element const& rhs) {
return std::visit(Eq{}, lhs, rhs);
}