开发者

C++ inheritance question

I have the following problem in application arch开发者_如何学JAVAitecture and am willing to solve it (sorry for a lot of text).

I am building a game engine prototype and I have base abstract class AbstractRenderer (I will use C++ syntax, but still the problem is general).

Assume there are some derived implementations of this renderer, let's say DirectxRenderer and OpenglRenderer.

Now, let's say that only one of these renderers (let's stick to DirectX-based) has a member called IDirect3D9Device* m_device; Obviously at this point everything is fine - m_device is used internally in DirectxRenderer and shouldn't be exposed in the abstract AbstractRenderer superclass.

I also add some abstract rendering interface, for instance IRenderable. It means simply one pure virtual method virtual void Render(AbstractRenderer* renderer) const = 0;


And this is the place where some problems start. Assume I am modelling some scene, so, this scene will probably have some geometrical objects in it.

I create abstract superclass AbstractGeometricalObject and derived DirectX-based implementation DirectxGeometricalObject. The second one would be responsible for storing pointers to DirectX-specific vertex & index buffers.

Now - the problem.

AbstractGeometricalObject should obviously derive the IRenderable interface, because it's renderable in logical terms.

If I derive my DirectxGeometricalObject from AbstractGeometricalObject, the first one should have virtual void Render(AbstractRenderer* renderer) const { ... } method in it, and that Abstract... stuff brings some troubles.

See the code for better explanation:

And for now my classes look the following way:

class AbstractGeometricalObject : public IRenderable {
    virtual void Render(AbstractRenderer* renderer) const { ... }
};

class DirectxGeometricalObject : public AbstractGeometricalObject {

    virtual void Render(AbstractRenderer* renderer) const {

    // I think it's ok to assume that in 99 / 100 cases the renderer
    // would be a valid DirectxRenderer object

    // Assume that rendering a DirectxGeometricalObject requires
    // the renderer to be a DirectxRenderer, but not an AbstractRenderer
    // (it could utilize some DX-specific settings, class members, etc

    // This means that I would have to ***downcast*** here and this seems really
    // bad to me, because it means that this architecture sucks

    renderer = dynamic_cast<DirectxRenderer*>(renderer);

    // Use the DirectX capabilities, that's can't be taken out
    // to the AbstractRenderer superclass
    renderer.DirectxSpecificFoo(...);
}

I know I'm probably worrying too much, but this downcast in such a simple case means that I could be forced to make lots of downcasts if my application grows.

Definitely, I would like to avoid this, so please, could you advice me something better in design terms / point out my errors.

Thank you


This might be a situation where the template pattern (not to be confused with C++ templates) comes in handy. The public Render in the abstract class should be non-virtual, but have it call a private virtual function (e.g. DoRender). Then in the derived classes, you override DoRender instead.

Here's an article that goes into great depth describing the use of template pattern with private virtual functions.

Edit:

I started to put together an example of what I meant, and it seems like there's actually a broader flaw in the architecture. Your use of AbstractRenderer is somewhat frivolous since you're forcing each geometricalobject to be intimately aware of a particular renderer type.

Either the renderer should be able to work off the public methods of Renderables, or Renderables should be able to work off the public methods of the Renderer. Or perhaps you can give the concrete renderers a Renderable factory if there really needs to be such an intimate connection. I'm sure there are some other patterns that would fit well, too.


I don't see what your code wants to achieve. You derive Renderable objects to DirectXRenderables and OpenGLRenderables and then provide OpenGL or DirectX functionality in something derived from Renderer. A specific thing uses another specific thing so to speak. It would seem much more reasonable to identify general rendering functions, make them pure virtual members of your abstract renderer and implement them in DirectXRenderer and OpenGLRenderer. Then a IRenderable would have a member function draw roughly like this:

void draw(const AbstractRenderer& r) {
  //general stuff
  r.drawLine(...);
  //only possible on directX
  if(DirectxRenderer rx = dynamic_cast<DirectxRenderer*>(r)) {
  //...
  } else {
    //throw exception or do fallback rendering in case we use something else
  }
} 


Using templates, you could split the IRendable into two classes, one for each of the two renderer types. This is probably not the best answer, but it does avoid the need for the dynamic cast:

template <typename RendererType>
struct IRenderable {
    virtual void Render(RendererType* renderer) const = 0;
}

template <typename RendererType>
class AbstractGeometricalObject : public IRenderable<RendererType> {
    virtual void Render(RendererType* renderer) const { ... }
};

class DirectxGeometricalObject : public AbstractGeometricalObject<DirectxRenderer> {
  // this class will now require a void Render(DirectxRenderer* renderer)
}


Use a setter to set the renderer var and cast it to the proper type in that one place.


See if the Bridge design pattern helps you: "Decouple an abstraction from its implementation so that the two can vary independently." In your example, AbstractGeometricalObject would point to an implementation, a pure virtual interface with platform-specific subclasses. The tricky part is taking the time to discover that interface.


Let's distance from compilers and consider theory. If DirectxGeometricalObject::Render expects DirectxRenderer as parameter and not any AbstractRenderer, then some other OtherGeometricalObject::Render will probably expect OtherRenderer object as parameter.

So, different implementations of AbstractGeometricalObject have different signatures of their Render methods. If they are different, then there is no purpose in defining the virtual AbstractGeometricalObject::Render.

If you declare AbstractGeometricalObject::Render(AbstractRenderer*), then you should be able to pass any renderer to any geometrical object. In your case, you can't because dynamic_cast would fail.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜