在 VisitorPattern 中结合访问者
Combine visitors in VisitorPattern
我根据cpppaterns.com实现了访问者模式来遍历一棵树并根据节点类型应用特定的动作。 我总是需要以相同的顺序遍历树,因此我将决定接下来访问哪个child的逻辑移到了基础class[=10中=].个人访问者对树节点进行特定操作,然后调用基Visitor
中的visit
方法。到目前为止效果很好。
在某些情况下,需要在一棵树上执行多个访问者(例如,VisitorRewriteX
、VisitorPrintX
)之间实施的操作。天真的方法是按顺序执行访问者。然而,这需要多次遍历树,效率低下。
当然,我也可以创建一个新的访问者(例如,VisitorRewritePrintX
),它只调用其他两个访问者——但我认为这不是干净的代码,也不灵活。是否有一些更通用的方式(例如,一种模式)可以帮助我以某种方式允许 'stacking' 在不同访问者中实施的操作?
有多种方法可以做到这一点,一种方法是拥有一个通用的访问者,您可以为其注册每次访问都会调用的处理程序:
struct visitor {
visitor(std::initializer_list<std::function<void(int)>> cbs) : callbacks(cbs) {
}
void visit(int val) {
for( auto &cb: callbacks ) {
cb(val);
}
}
std::vector<std::function<void(val)>> callbacks;
};
由于您对不同的元素有 visit
函数,您可能需要将其更改为具有相应成员函数的 class 而不是使用 std::function
另一种方法是使用可变模板参数:
struct handler_a {
static void visit(int val) {
std::cout << "handler_a: " << val << std::endl;
}
};
struct handler_b {
static void visit(int val) {
std::cout << "handler_b: " << val << std::endl;
}
};
template <class ...Handlers>
struct visitor {
void visit(int val) {
(Handlers::visit(val), ...);
}
};
int main() {
visitor<handler_a,handler_b> v;
v.visit(10);
return 0;
}
可变参数方法的优点是没有循环,因为编译器在编译时为调用创建代码。但它的缺点是您无法在遍历时使用这些处理程序保存临时信息,因为它们是静态函数。但是您也许可以使用与遍历对象一起传递的上下文对象来解决这个问题。
如果您愿意,您可以使用 using visitor_a_b = visitor<handler_a,handler_b>
作为您的访客集的别名。
我省略了继承以使代码最少,因此您需要将它们添加回去才能使 virtual void accept(visitor& v) override
正常工作。
基本上,我会做类似的事情:
struct IVisitor
{
virtual ~IVisitor() = default;
virtual void visit(NodeA&) = 0;
virtual void visit(NodeB&) = 0;
// ...
};
struct ITraversal : IVisitor
{
// Traversal logic
void visit(NodeA& n) final { visit(n.left); action(n); visit(n.right); }
void visit(NodeB& n) final { action(n); visit(n.next); }
// Actions
virtual void action(NodeA&) = 0;
virtual void action(NodeB&) = 0;
// ...
};
struct VisitorRewriteX : ITraversal
{
void action(NodeA&) override;
void action(NodeB&) override;
// ...
};
struct VisitorPrintX : ITraversal
{
void action(NodeA&) override;
void action(NodeB&) override;
// ...
};
// To allow type T with generic action
template <typename T>
struct VisitorT : ITraversal
{
VisitorT() = default;
VisitorT(const T& t) : t(t) {}
void action(NodeA& n) override { t.action(n); }
void action(NodeB& n) override { t.action(n); }
// ...
T t;
};
template <typename ... Ts>
struct Combine
{
Combine() = default;
Combine(Ts... args) : visitors{args...} {}
template <typename Node>
void action(Node& n) override {
std::apply([&](auto&...visitor){ (visitor.action(n), ...); }, visitors);
}
private:
std::tuple<Ts...> visitors;
};
然后使用
VisitorRewriteX
、VisitorPrintX
或 VisitorT<Combine<VisitorRewriteX, VisitorPrintX>>
我根据cpppaterns.com实现了访问者模式来遍历一棵树并根据节点类型应用特定的动作。 我总是需要以相同的顺序遍历树,因此我将决定接下来访问哪个child的逻辑移到了基础class[=10中=].个人访问者对树节点进行特定操作,然后调用基Visitor
中的visit
方法。到目前为止效果很好。
在某些情况下,需要在一棵树上执行多个访问者(例如,VisitorRewriteX
、VisitorPrintX
)之间实施的操作。天真的方法是按顺序执行访问者。然而,这需要多次遍历树,效率低下。
当然,我也可以创建一个新的访问者(例如,VisitorRewritePrintX
),它只调用其他两个访问者——但我认为这不是干净的代码,也不灵活。是否有一些更通用的方式(例如,一种模式)可以帮助我以某种方式允许 'stacking' 在不同访问者中实施的操作?
有多种方法可以做到这一点,一种方法是拥有一个通用的访问者,您可以为其注册每次访问都会调用的处理程序:
struct visitor {
visitor(std::initializer_list<std::function<void(int)>> cbs) : callbacks(cbs) {
}
void visit(int val) {
for( auto &cb: callbacks ) {
cb(val);
}
}
std::vector<std::function<void(val)>> callbacks;
};
由于您对不同的元素有 visit
函数,您可能需要将其更改为具有相应成员函数的 class 而不是使用 std::function
另一种方法是使用可变模板参数:
struct handler_a {
static void visit(int val) {
std::cout << "handler_a: " << val << std::endl;
}
};
struct handler_b {
static void visit(int val) {
std::cout << "handler_b: " << val << std::endl;
}
};
template <class ...Handlers>
struct visitor {
void visit(int val) {
(Handlers::visit(val), ...);
}
};
int main() {
visitor<handler_a,handler_b> v;
v.visit(10);
return 0;
}
可变参数方法的优点是没有循环,因为编译器在编译时为调用创建代码。但它的缺点是您无法在遍历时使用这些处理程序保存临时信息,因为它们是静态函数。但是您也许可以使用与遍历对象一起传递的上下文对象来解决这个问题。
如果您愿意,您可以使用 using visitor_a_b = visitor<handler_a,handler_b>
作为您的访客集的别名。
我省略了继承以使代码最少,因此您需要将它们添加回去才能使 virtual void accept(visitor& v) override
正常工作。
基本上,我会做类似的事情:
struct IVisitor
{
virtual ~IVisitor() = default;
virtual void visit(NodeA&) = 0;
virtual void visit(NodeB&) = 0;
// ...
};
struct ITraversal : IVisitor
{
// Traversal logic
void visit(NodeA& n) final { visit(n.left); action(n); visit(n.right); }
void visit(NodeB& n) final { action(n); visit(n.next); }
// Actions
virtual void action(NodeA&) = 0;
virtual void action(NodeB&) = 0;
// ...
};
struct VisitorRewriteX : ITraversal
{
void action(NodeA&) override;
void action(NodeB&) override;
// ...
};
struct VisitorPrintX : ITraversal
{
void action(NodeA&) override;
void action(NodeB&) override;
// ...
};
// To allow type T with generic action
template <typename T>
struct VisitorT : ITraversal
{
VisitorT() = default;
VisitorT(const T& t) : t(t) {}
void action(NodeA& n) override { t.action(n); }
void action(NodeB& n) override { t.action(n); }
// ...
T t;
};
template <typename ... Ts>
struct Combine
{
Combine() = default;
Combine(Ts... args) : visitors{args...} {}
template <typename Node>
void action(Node& n) override {
std::apply([&](auto&...visitor){ (visitor.action(n), ...); }, visitors);
}
private:
std::tuple<Ts...> visitors;
};
然后使用
VisitorRewriteX
、VisitorPrintX
或 VisitorT<Combine<VisitorRewriteX, VisitorPrintX>>