General polymorphism issue

Jul 30, 2011 at 12:33pm
Hello everyone, I recently got interested (again) in the concept of abstract classes and interfaces. I'm currently designing a simple game using SDL and I decided to use these concepts. So for now I got 3 major interfaces: Renderable, Controllable and Collidable; and a few implementations (like Character, Wall, Zone, etc.). I want to unite them both under a common abstract class GameObject.

The problem is I don't want the GameObject class to support all the interfaces I mentioned above, because this means that every class which extends the common class have to implement all the functions from all the interfaces. Thus, for example, the Wall class, which is a derived class of GameObject, have to implement the Controllable interface without being controllable.

I want the objects to receive functionality from the interfaces they inherit, but and the same time I want them to be accessible only via the common interface GameObject.

Or said in other words:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class Renderable
{
    public:
        virtual ~Renderable();
        virtual void render(SDL_Surface *) = 0;
        // some other pure virtual functions
};

class Controllable
{
    public:
        virtual ~Controllable();
        virtual void move(const int newX, const int newY) = 0;
        // some other pure virtual functions
};

class GameObject
{
    public:
        virtual bool isRenderable() = 0;
        virtual bool isControllable() = 0;
        // some other functions
    protected:
        // some common variables
}

class Wall : public GameObject, public Renderable
{
    public:
        Wall(/* args */);
        ~Wall();
        bool isRenderable();
        bool isControllable();
        void render(SDL_Surface *);
        // more functions
};

class Character : public GameObject, public Renderable, public Controllable
{
    public:
        Character(/* args */);
        ~Character();
        bool isRenderable();
        bool isControllable();
        void render(SDL_Surface *);
        void move(const int newX, const int newY);
        // more functions
};

//---------------------

int main()
{
    GameObject *object[100];
    object[0] = new Character(/* args */);
    object[1] = new Wall(/* args */);
    // etc.

    // So GameObject has no render() or move() functions
    // but both Character and Wall have render() functions
    // and Character has move() function
    // How can I use this kind of code?

    if( object[0]->isRenderable() )
        object[0]->render(/*some SDL surface*/);

    if( object[1]->isMovable() )
        object[1]->move(/*new coords*/);
}


I don't wanna use type cast like:
1
2
3
4
5
if( object[0]->isRenderable() )
{
    Renderable * renderableObject = (Renderable *) object[0];
    renderableObject->render(/* ... */);
}

because it's risky and it almost never works.

Also, as I stated above, I don't wanna have all the functions from all the interfaces declared in GameObject and implemented in *every* derived class, because half of the implementations would be empty functions which do nothing.

I hope you understood me. I'm still a bit confused about the whole thing.
Last edited on Jul 30, 2011 at 12:52pm
Jul 30, 2011 at 12:58pm
To call render() through a GameObject pointer, there has got to be an entry for the function in the vtable. And if you have an entry in the vtable, it's got to point at a valid function if you want to instantiate an instance of the object.

If GameObject inherits Renderable, Controllable, Moveable, ... you should be able to implement the necessary stubs in GameObject rather than all derived classes.

You could also make isRenderable, isControllable, isMoveable, ... non-virtual member of GameObject and control the values using bools (or bit flags) which are initialized by the constructor (I assume here that a given type of object in always moveable or never moveable).

The compiler won't pick up missing implementations (e.g. if each derived class inherits from Renderable, etc. you'll get a compile error if you forget to implement render(), etc), but you won't need to cast.
Jul 30, 2011 at 1:54pm
To call render() through a GameObject pointer, there has got to be an entry for the function in the vtable. And if you have an entry in the vtable, it's got to point at a valid function if you want to instantiate an instance of the object.

That's the point. I don't need an entry point for render() in the GameObject class, because not all GameObject-s need a render() method, plus, I have an entry point in the Renderable interface. The problem is how to reach the Renderable (or any of the other interfaces) only through a GameObject pointer.
And I think I've got the solution. I don't want to use type-casting because the whole vtable gets messed when you switch to another class type using the same pointer, but I just found out there's something called dynamic-casting and it's designed to get a reference to a sub-class type of object using a base class pointer (or reference).

So the problematic code would be like this:
1
2
3
4
5
Renderable * renderableObject = dynamic_cast<Renderable *>(object[0]);
if(renderableObject)    // if the dynamic-cast fails because there's no such sub-class it return NULL pointer value
    renderableObject->render(/* ... */); // this time the compiler won't complain
                                         // there's a chance that object[0] doesn't support the Renderable interface though
                                         // that's why the if statement 


And by the way the funtions isRenderable(), isControllable(), isMoveable() are there just to test if the corresponding object supports the interface in question. They have no other role. But as long as the dynamic-cast returns NULL if there's no such sub-class of the particular base class there's no need of these.

So, I think I answered my question :D But if you got a more brilliant solution, please, write in this thread.
Last edited on Jul 30, 2011 at 3:13pm
Jul 30, 2011 at 2:52pm
I get what you mean. But you can only reach a function through a GameObject pointer if it's there. Otherwise you have to do a cast, which I thought you didn't want to do.

Using isRenderable() with a dynamic_cast is unnecessary. Use either

1
2
3
4
5
6
if( object[0]->isRenderable() )
{
    Renderable * renderableObject = static_cast<Renderable *>(object[0]);
    renderableObject->render(/* ... */); 
}
 
or

1
2
3
4
5
Renderable * renderableObject = dynamic_cast<Renderable *>(object[0]);
if(NULL !=  renderableObject)
{
    renderableObject->render(/* ... */); 
}
You can then eliminate isRenderable(), etc. if you have no other use for them.

Andy

P.S. Note that you should avoid C-style casts. Esp. with up and down casts. With these types of cast, the behaviour can change depending on whether the class definition is available or not. If only a forward declaration of a class is visible, the C-style cast will act like a reinterpret_cast rather than a static_cast. A static_cast needs the class definition so it can offset the this pointer by the correct amount.



Last edited on Jul 30, 2011 at 2:53pm
Jul 30, 2011 at 6:43pm
Yes, I realised I don't need isRenderable(), etc. with dynamic_cast. I simply wasn't aware of the fact there was a dynamic_cast when I was writing my first post. Now it works like a charm.

P.S. Note that you should avoid C-style casts. Esp. with up and down casts. With these types of cast, the behaviour can change depending on whether the class definition is available or not. If only a forward declaration of a class is visible, the C-style cast will act like a reinterpret_cast rather than a static_cast. A static_cast needs the class definition so it can offset the this pointer by the correct amount.

I know there are important things in C++ I'm missing. It's just I got used to C style code writing, for example, I still use printf() instead of cout and I define my strings like char[]. I should familiarize myself with what C++ has to offer I just never find the right moment to do it. Thanks for the hint.
Last edited on Jul 30, 2011 at 6:43pm
Topic archived. No new replies allowed.