3

Say in a game engine, you have Objects that composited of different components. One of these is a RenderComponent which can be either a Mesh, a Sprite or a Light.

Now, all these are vastly different classes with non-complatible interfaces, so deriving them all from RenderComponent makes little sense. Also, an Object can only have one RenderComponent at a time, so you saving three pointers in Object, two of which must always be nullptr, seems wasteful. So what I came up with is using RenderComponent to store a void* pointer and the type.

class RenderComponent{
public:
    enum class RenderType{
        Mesh, Sprite, Light
    };

    RenderType getType(){ return type_; }

    Mesh* asMesh(){ 
        if(type_ == RenderType::Mesh)
            return static_cast<Mesh*>(pointer_.get()) 
        else
            return nullptr; 
    } /* the same stuff for Sprite and Light */

    RenderComponent(unique_ptr<Mesh>&& pointer) :
        pointer_(std::move(pointer)),
        type_(RenderType::Mesh)
    {} /* the same stuff for Sprite and Light */

private:
    unique_ptr<void> pointer_;
    RenderType type_;
}

It should work as intended, but I feel like this is an anti-pattern. I'm basically discarding all typesafe aspects of C++ and then reimplement them myself. This can't be right.

So, is this an anti-pattern? What are common workarounds for this problem? Am I missing something obvious? Or is this a known, but (for good reason) only rarely used pattern?

1

2 Answers 2

6

The correct way to approach this is to use RTTI and dynamic_cast. This does nearly the same thing as what you are doing, but in a type-safe, language approved manner. (dynamic_cast throws if you try to case objects into things they are not related to.)

One reason people write code like this is that dynamic_cast came into the language after people had been using it a while. Too often, developers don't keep up with language improvements and continue doing things they way they are used to. So in 1994, this might have been perfectly good code.

The other reason people write code like this is because of the widespread belief that RTTI hurts performance. The thought is that because RTTI has to store type information for every single class, it "wastes memory". You often see this attitude with people who do embedded development, or in game development. Also, dynamic_cast requires exception handling, and the same groups often turn this off exception handling.

There is something behind this in that RTTI can increase your memory footprint. This particularly happens when you make heavy use of templated classes like the STL as these can cause the compiler to create large numbers of classes under the covers, all of which get type information. On one project I worked on like this, we saw nearly half a megabyte lost to type information. My feeling, though, is that if you have a tight memory budget, you might be better off refactoring the templated classes into something that don't use templates.

Because of the general dislike of RTTI in the game industry, third party engines will almost never use it. This is more about supplying what the customer is asking for than good engineering.

This all requires an inheritance tree, of course. You say:

Now, all these are vastly different classes with non-complatible interfaces, so deriving them all from RenderComponent makes little sense.

But I find this questionable, because your first line is:

One of these is a RenderComponent which can be either a Mesh, a Sprite or a Light.

Emphasis mine. "be" seems to denote an "is-a" relationship, which is a classic sign of a class hierarchy.

I don't think this is necessarily an "anti-pattern", though, as this is a way that many successful projects have approached the problem, and it's something that ended up being codified into the language.

3
  • dynamic_cast is how I currently handle it, and I'm all for RTTI in general, I try to emphasise it as much as possible in this project. But yes, it just feels very inefficient to check the type of something by repeatedly casting it until you get what you searched for.
    – iFreilicht
    Commented Aug 17, 2014 at 17:21
  • 1
    Under the covers, it probably just boils down to a bunch of comparisons anyway.
    – user53141
    Commented Aug 17, 2014 at 19:26
  • 4
    dynamic_cast might throw an exception if casting to a reference type, if casting to a pointer type it will instead return a null pointer of that type, if it fails. (CPP standard §5.2.7.9) Commented Aug 17, 2014 at 19:36
1

This is anti-OO, but entity-component-system design is expressly not object oriented. I do not think that this is a bad practice in general for software that is based on CES, but in your example it is bad practice. In my opinion, the rendering function should probably try and get a sort of MeshComponent from the Entity and render that. If the component is not there, then return nullptr. For different rendering types you can have derived rendercomponents, too. Or different render systems, which might be an even clearer approach.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.