views:

76

answers:

4

I've been making a game which uses the Box2D physics engine, and I've come across some weirdness with the stack pointer (ESP) and multiple inheritance. I've managed to reproduce it in a minimal amount of code, and it seems that the order in which I declare the classes to be used in multiple inheritance seems to dictate whether the program crashes or not.

#include <iostream>
#include <string.h>

using namespace std;

class IPhysicsObject
{
public:
    virtual void Collide(IPhysicsObject *other, float angle, int pos)=0;
};

class IBoardFeature
{
public:
    IBoardFeature(){};
    ~IBoardFeature(){};

    virtual bool OnAttach(int x){ return true; }
    virtual bool Update(int x, float dt)=0;
};

/*
class CScorezone : public IBoardFeature, public IPhysicsObject // this breaks !!!
class CScorezone : public IPhysicsObject, public IBoardFeature // this works !!!
*/
class CScorezone : public IBoardFeature, public IPhysicsObject
{
public:
    CScorezone(){}
    ~CScorezone(void){}

    virtual bool Update(int x, float dt)
    {
        return true;
    }

    virtual void Collide(IPhysicsObject *other, float angle, int pos)
    {
    }

    virtual bool OnAttach(int x){ return true; }
};


int main(int argc, char *argv[]) 
{
    CScorezone *scoreZone = new CScorezone();
    CScorezone *otherZone = new CScorezone();

    void *voidZone = scoreZone;
    IPhysicsObject *physZone = static_cast<IPhysicsObject*>(voidZone);
    physZone->Collide(otherZone, 10, 1);

    delete scoreZone;
    delete otherZone;

    // wait for user input
    int x;
    cin >> x;
    return 0;
}

Running this in debug mode causes the following error

Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.

When I step in to the following line of code:

physZone->Collide(otherZone, 10, 1);

I notice it's going into CScoreZone::OnAttach, not CScoreZone::Collide. Why is this? WHen I change the order of inheritance for CScoreZone, it works fine

class CScorezone : public IPhysicsObject, public IBoardFeature

I'm running on VS2005 SP2 (8.0.50727.768) on Windows XP. Any ideas?

+2  A: 

The problem is that you cast the pointer to void* first. The compiler doesn't know then how to perform static cast for the pointer. It needs to change the pointer value during the cast if you use multiple inheritance to use second superclass virtual table. Just cast the pointer back to CScoreZone* before using static_cast.

shura
Hmm, that explains the problem - thanks for that. Problem is, I don't know for certain this will be a CScoreZone* at this point - all I know is it will be an IPhysicsObject.Is there any way I can push it in to a void*, get it out as an IPhysicsObject*, and maintain the vtable integrity?
XwipeoutX
Use `static_cast<void*>(static_cast<IPhysicsObject*>(someZone))` to comply with *§5.2.9/10*.
Georg Fritzsche
Yeah, that did it. Thanks!
XwipeoutX
+3  A: 
Nikolai N Fetissov
I guess the void casts are there to demonstrate a problem - maybe some passing of a pointer through a callback?
Georg Fritzsche
Yeah, the void casts are there to reproduce it - to find out what objects are colliding, I fill in a "user data" member (which is void*). I then need to extract that data when the things collide.
XwipeoutX
Thanks for that - this would have done the trick too. I decided static casting to IPhysicsObject before going to void* was a bit simpler, though.
XwipeoutX
A: 

Nikolai told you how to avoid casting in the first place with your example given. However if you do need to do a typecast, when working with objects always use dynamic_cast, which does runtime type checking.

Ozan
This is unnecessary if you use `static_cast` correctly.
Georg Fritzsche
+1  A: 

Well, in your code you seem to be deliberately destroying the integrity of a hierarchical cast by using void * as an intermediate type in the cast. ScoreZone * is cast to void * first and then cast to IPhysicsObject *. What you get as the result is undefined behavior.

Why are you doing this? And what did you expect will happen?

AndreyT
I need the void* as that's how Box2d stores related data of its physics objects. I might change it so I store a wrapper class holding the IPhysicsObject in it, which I can easily cast between without these issues
XwipeoutX