如何使访问者接口适应迭代器接口?
我想知道是否有一个好的设计模式或习惯用法来实现以下内容:
您有一个仅提供访问者界面的现有类,如下所示
类访客{ 民众: 虚拟 ~Visitor() { } 虚拟无效访问(节点*n)= 0; }; 类树{ 民众: 无效接受(访客*v); };
并且您希望有一个可以按如下方式使用的接口,该接口应该按照访问者调用其
visit
函数的顺序迭代树。for(迭代器 it(...), ite(...); it != ite; ++it) { /* 进程节点 */ }
问题似乎是,当我们只调用 visit
时,我们就失去了控制,并且无法暂时“返回”循环体来执行一个节点的操作。这看起来应该在现实世界的程序中经常发生。知道如何解决吗?
I'm wondering whether there is a good design pattern or idiom to realize the following:
You have an existing class that provides only a visitor interface, as follows
class Visitor { public: virtual ~Visitor() { } virtual void visit(Node *n) = 0; }; class Tree { public: void accept(Visitor *v); };
And you want to have an interface that can be used as follows, which should iterate through the tree in the same order that the visitor would have its
visit
function called.for(iterator it(...), ite(...); it != ite; ++it) { /* process node */ }
The problem appears to be that when we just call visit
, we are out of control, and can't temporarily "go back" to the loop body to execute the action for one node. This looks like it should occur regularly in real world programs. Any idea how to solve it?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
在一般情况下,我认为这是不可能的,至少不可能。
至少按照通常的定义,迭代器期望处理同构集合。即,迭代器通常定义如下:
...因此特定迭代器只能处理一种特定类型的元素。要处理不同类型,您最多可以做的是创建一个指向基类(指向基类的指针/引用)的迭代器,并让它处理派生类的对象。
相比之下,编写这样的访问者非常容易:
它可以访问
OneType
或AnotherType
的节点,即使两者完全无关。基本上,对于能够访问的每种不同类型的类,您的 Visitor 类中都有一个Visit
成员函数。从稍微不同的方向来看,迭代器基本上是一种专门形式的访问者,仅适用于一种类型的对象。您交换了对访问模式的更多控制权,以换取失去访问不相关类型的对象的能力。
如果您只需要处理一种类型(尽管一种类型可能是基类,并且访问的对象具有各种派生类型),那么显而易见的方法是构建一个访问对象的“桥梁”类(<代码>Tree 节点,在您的示例中),当调用它的
visit
时,它只是将其正在访问的节点的地址复制到某个支持迭代器的集合中:使用这将是两个步骤过程:首先像访问者通常那样访问节点,然后将感兴趣的节点收集在一起,您可以迭代它们,就像您对任何其他提供迭代器的集合一样。此时,您的访问模式仅受您在桥中使用的集合提供的迭代器类的限制。
In the general case, I don't think it's possible, at least not cleanly.
At least as it's usually defined, an iterator expects to deal with a homogeneous collection. I.e., an iterator is normally defined something like:
...so a specific iterator can only work with elements of one specific type. The most you can do to work with differing types is create an iterator to (a pointer/reference to) a base class, and let it deal with objects of derived classes.
By contrast, it's pretty easy to write a visitor like this:
This can visit nodes of either
OneType
orAnotherType
, even if the two are completely unrelated. Basically, you have oneVisit
member function in your Visitor class for every different type of class that it will be able to visit.Looked at from a slightly different direction, an iterator is basically a specialized form of visitor that only works for one type of object. You exchange a little more control over the visitation pattern in exchange for losing the ability to visit unrelated types of objects.
If you only need to deal with one type (though that one type may be a base class, and the visited objects are of various derived types), then the obvious method would be to build a "bridge" class that visits objects (
Tree
nodes, in your example), and when itsvisit
is called, it just copies the address of the node it's visiting into some collection that supports iterators:Using this would be a two-step process: first visit the nodes like a visitor normally would, then having collected together the nodes of interest you can iterate through them just like you would any other collection that provides iterators. At that point, your visitation pattern is limited only by the class of iterator provided by the collection you use in your bridge.
我在现实环境中遇到了这个问题,使用提供访问者接口的 R 树实现,而我需要一个迭代器接口。仅当您接受将所有结果存储在集合中时,Jerry 的上述建议才有效。如果您的结果集很大并且您实际上不需要存储它们,这可能会导致高内存消耗。
一种肯定有效的解决方案是在单独的线程中启动访问者并开始等待条件变量的结果。当进行访问调用时,您将当前结果存储到共享临时位置,并使用另一个条件变量来等待下一个请求。在您自己等待之前,您可以向调用者(主)线程的条件变量发出信号。然后,实现迭代器接口的调用者可以返回存储在临时位置的值。在下一次迭代期间,它可以向访问者线程的条件变量发出信号,并自行等待下一个项目。不幸的是,如果您针对每个项目执行此操作,成本会有些高。您可以缓冲一些项目以提高性能。
我们真正需要的是一个额外的堆栈并在两个上下文之间交替。这种抽象是由协程提供的。在 C++ 中,boost::coroutine 提供了一个干净的实现。下面我提供了一个完整的示例,说明如何将访问者模式改编为迭代器模式。
I had this problem in a real-world setting with an R-tree implementation that provided a visitor interface, whereas I needed an iterator interface. The suggestion by Jerry above works only if you can accept storing all the results in a collection. That may result in high memory consumption if your result set is huge and you don't really need to store them.
One solution that will work for sure is to launch the visitor in a separate thread and start waiting on a conditional variable for the results. When a visit call is made, you store the current result into a shared temp location, and use another conditional variable to wait for the next request. You signal the caller (main) thread's conditional variable before you wait on your own. The caller, which is implementing the iterator interface can then return the value stored at the temp location. During the next iteration, it could signal the visitor thread's conditional variable, and wait on its own for the next item. Unfortunately, this is somewhat costly if you do it on a per-item basis. You can buffer some items to improve the performance.
What we really need is an extra stack and to alternate between two contexts. This abstraction is provided by coroutines. In C++, boost::coroutine provides a clean implementation. Below I include a full example of how visitor pattern can be adapted into an iterator pattern.
在访问者实现中构建遍历逻辑确实不灵活。可以通过访问者组合器<来干净地分离遍历复合结构和访问。 /a> (还有其他论文,请随意谷歌搜索)。
这些关于同一主题的幻灯片您可能也会感兴趣。他们解释了如何获得干净的语法 à la
boost::spirit
规则。Building traversal logic in the visitors implementations is indeed not flexible. A usable way to cleanly separate traversing composite structures from visitation may be done via visitor combinators (there are other papers, feel free to google for them).
These slides about the same topic may also be of interest. They explain how to get clean syntax à la
boost::spirit
rules.