Неверный ESP при использовании множественного наследования в C++ (VS2005)
Я делал игру, которая использует физический движок 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 ответа
Проблема в том, что сначала вы приводите указатель на void*. Компилятор не знает, как выполнить статическое приведение указателя. Необходимо изменить значение указателя во время приведения, если вы используете множественное наследование для использования второй виртуальной таблицы суперкласса. Просто приведите указатель назад к CScoreZone* перед использованием static_cast.
Вам не нужно назначать CScorezone*
в void*
а затем бросить его IPhysicsObject*
, поскольку CScorezone
это IPhysicsObject
Вы можете просто назначить базовый указатель:
IPhysicsObject *scoreZone = new CScorezone();
IPhysicsObject *otherZone = new CScorezone();
Вам также не хватает общедоступного виртуального деструктора в IPhysicsObject
декларация.
Редактировать:
Я описываю ситуацию обратного вызова, как вы описываете в комментариях (проходя через некоторые 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( ... );
}
Ну, в вашем коде вы, кажется, сознательно разрушаете целостность иерархического приведения, используя void *
в качестве промежуточного типа в актерском составе. ScoreZone *
брошен в void *
сначала, а затем приведен к IPhysicsObject *
, В результате вы получаете неопределенное поведение.
Почему вы это делаете? А что вы ожидали, что произойдет?
Николай рассказал вам, как избежать кастинга, в первую очередь, с вашим примером. Однако, если вам нужно выполнить приведение типов, при работе с объектами всегда используйте dynamic_cast, который выполняет проверку типов во время выполнения.