Неверный ESP при использовании множественного наследования в C ++ (VS2005) - PullRequest
2 голосов
/ 24 января 2010

Я делаю игру, которая использует физический движок Box2D, и я столкнулся с некоторыми странностями с указателем стека (ESP) и множественным наследованием. Мне удалось воспроизвести его в минимальном объеме кода, и кажется, что порядок, в котором я объявляю классы для использования в множественном наследовании, похоже, определяет, происходит ли сбой программы или нет.

#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;
}

Запуск этого в режиме отладки вызывает следующую ошибку

Ошибка проверки времени выполнения # 0 - значение ESP не был сохранен должным образом через вызов функции. Обычно это результат вызова объявленной функции с одним соглашением о вызовах с указатель функции объявлен с другое соглашение о вызовах.

Когда я перехожу к следующей строке кода:

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

Я заметил, что это происходит в CScoreZone :: OnAttach, а не в CScoreZone :: Collide. Почему это? Когда я меняю порядок наследования для CScoreZone, он отлично работает

class CScorezone : public IPhysicsObject, public IBoardFeature

Я работаю на VS2005 SP2 (8.0.50727.768) на Windows XP. Есть идеи?

Ответы [ 4 ]

4 голосов
/ 24 января 2010

Вам не нужно присваивать CScorezone* для void*, а затем приводить его к IPhysicsObject*. Поскольку CScorezone является IPhysicsObject, вы можете просто назначить базовый указатель:

    IPhysicsObject *scoreZone = new CScorezone();
    IPhysicsObject *otherZone = new CScorezone();

Вам также не хватает общедоступного виртуального деструктора в объявлении IPhysicsObject.

Edit:

В ситуации обратного вызова, которую вы описываете в комментариях (проходя через некоторые C api?), Я бы использовал простой struct с указателем на полиморфный тип, чтобы избежать неопределенных приведений, что-то вроде:

    // one more level of indirection
    struct cb_data
    {
        IPhysicsObject* target;
    };

    // callback function
    int callback( void* data )
    {
        const cb_data& cbd( *static_cast<cb_data*>( data ));

        return cbd.target->Collide( ... );
    }
3 голосов
/ 24 января 2010

Проблема в том, что вы сначала приводите указатель на void *. Компилятор не знает, как выполнить статическое приведение указателя. Необходимо изменить значение указателя во время приведения, если вы используете множественное наследование для использования второй виртуальной таблицы суперкласса. Просто приведите указатель назад к CScoreZone * перед использованием static_cast.

1 голос
/ 24 января 2010

Что ж, в вашем коде вы, кажется, сознательно разрушаете целостность иерархического приведения, используя void * в качестве промежуточного типа в приведении. ScoreZone * сначала приводится к void *, а затем к IPhysicsObject *. В результате вы получаете неопределенное поведение.

Почему ты это делаешь? А что вы ожидали, произойдет?

0 голосов
/ 24 января 2010

Николай рассказал вам, как избежать кастования, в первую очередь, с вашим примером. Однако, если вам нужно выполнить приведение типов, при работе с объектами всегда используйте dynamic_cast, который выполняет проверку типов во время выполнения.

...