C ++:对访问者模式的怀疑

时间:2010-11-14 12:20:33

标签: c++ visitor-pattern double-dispatch

我知道访客模式是什么以及如何使用它;这个问题与此one不重复。


我有一个库,我把大部分可重复使用的代码放在我的大部分项目中。

我经常需要为某些类添加功能,但不会将这些新功能添加到库中。让我用一个真实的例子:

在这个lib中,我有一个由ShapeCircleShapePolygonShape继承的课程CompositeShape

我现在正在开发一个图形应用程序,我需要渲染这些Shape,但不想在核心render类中放置虚函数Shape,因为有些我使用Shape的项目不进行任何渲染,其他图形项目可以使用不同的渲染引擎(我在这个项目中使用Qt,但对于一个我使用OpenGL的游戏,因此{{ 1}}函数需要不同的实现。)

最着名的方法是使用访客模式,当然,这会让我产生一些怀疑:

任何库的任何类都需要像我的render一样进行扩展。大多数公共图书馆(大约所有公共图书馆)都没有为访客模式提供任何支持;为什么?我为什么要这样做?

访问者模式是一种在C ++中模拟Double Dispatching的方法。它在C ++中不是原生的,需要显式实现,使得类接口更复杂:我不认为Shape函数应该与我的类函数处于同一级别,我认为这就像打破抽象

使用applyVisitor明确上传Shape的费用更高,但对我而言,这看起来更清晰。


那么,我该怎么办?在我的所有库类中实现Double Dispatching?如果提供dynamic_cast的图书馆不是我的,但是在互联网上找到了一些GPL图书馆怎么办?

5 个答案:

答案 0 :(得分:14)

首先:“访问者模式是一种在C ++中模拟Double Dispatching的方法。”这是,嗯,不完全正确。实际上,双调度是多调度的一种形式,这是一种在C ++中模拟(缺失)多方法的方法。


是否应通过 添加虚拟功能 添加访问者 来实施类层次结构上的操作通过添加类与添加操作的概率:

  • 如果 类的数量不断变化 比操作次数更快, 使用虚拟功能 。这是因为添加一个类需要修改所有访问者。
  • 如果 类的数量相对稳定 与操作次数相比, 使用访问者 。这是因为添加虚函数需要更改层次结构中的所有类。

是的,许多图书馆没有访问者界面 当我们只看上面的推理时,如果类的数量经常变化,那就是正确的。也就是说,如果经常发布库,并且不断添加新类,那么提供访问者界面就没有多大意义,因为每次新版本带来新类时,使用该库的每个人都需要调整所有访问者。因此,如果我们只看上面的推理,那么如果lib的类层次结构中的类数很少或永远不会改变,那么访问者界面似乎才有用。

但是,对于第三方库,还有另一个方面:通常,用户无法更改库中的类。也就是说,如果他们需要添加操作, 他们可以执行此操作的唯一方法是添加访问者 - 如果库提供了挂钩让他们插入 因此,如果您正在编写库并且感觉用户应该能够向其添加操作,那么 您需要为他们提供一种方式将访问者插入您的lib

答案 1 :(得分:0)

对于我而言,这看起来不像访客模式。

我建议您有一个RenderableShape类聚合Shape对象,然后为每个形状创建子类。 RenderableShape将采用虚拟render方法。

如果要支持多个渲染引擎,可以使用RenderContext基类抽象绘制操作,每个渲染引擎都有子类,每个子类根据渲染引擎实现绘制操作。然后,您RenderableShape::renderRenderContext作为参数,并使用其抽象的API绘制它。

答案 2 :(得分:0)

所以有一个类xxxShape,它在某种程度上包含“驱动”渲染的信息。对于可能是中心的圆,半径,对于正方形的某些角坐标或某些角坐标。也许其他一些关于填充物和颜色的东西。

您不希望/无法更新这些类以添加实际的渲染逻辑,我认为您不这样做的原因是有效/不可避免的。

但是大概,你在课程上有足够的公共访问方法,可以让你获得“驾驶”信息,否则你就注定了。

所以在这种情况下为什么你不能只包装这些项目:

 CircleRenderer hasA Cicle, knows how to render Circles

等等。现在在Renderer类中使用Visitor模式。

答案 3 :(得分:0)

有许多可能的解决方案,但您可以这样做,例如:启动新的层次结构,在特定的Shapes中呈现Context

// contracts:

class RenderingContext {
public: virtual void DrawLine(const Point&, const Point&) = 0; 
    // and so on...
};

class ShapeRenderer {
public: virtual void Render(RenderingContext&) = 0;
};

// implementations:

class RectangleRenderer : public ShapeRenderer {
 Rectangle& mR;

public: 
 virtual void Render(RenderingContext& pContext) {
   pContext.DrawLine(mR.GetLeftLower(), mR.GetRightLower());
   // and so on...
 }

 RectangleRenderer(Rectangle& pR) : mR(pR) {}
};

答案 4 :(得分:0)

我完全理解你说的话,我也有同样的担忧。 问题是访客模式的定义不是很清楚,原来的解决方案是误导性的,恕我直言。这就是为什么这种模式有很多变化的原因。

特别是,我认为正确的实现应该支持遗留代码,我的意思是:你完全丢失了源代码的二进制文件,不是吗?这就是定义所说的:您永远不必更改原始数据结构。

我不喜欢visitA,visitB,visitWhatever,acceptA,acceptB,​​acceptWhatever的实现。这绝对是错的,恕我直言。

如果有机会,请查看an article I've written about this

它是Java,但如果您发现它对您的目的有用,则可以轻松移植到C ++。

我希望它有所帮助

干杯