Я пытаюсь создать собственный движок столкновений для академических целей, и я застрял в общей проблеме программирования на С ++. У меня уже есть все геометрии, которые работают должным образом, и тест столкновения также работает должным образом.
Движок использует эти 2 класса для создания очереди геометрии для тестирования:
class collidable;
template<typename geometry_type>
class collidable_object : public collidable;
Поскольку возможны несколько типов геометрии, я не хотел указывать вручную какие-либо столкновения для проверки.
Вместо этого я использовал эту «технику» для реализации двойной диспетчеризации:
class collidable
{
public:
typedef bool (collidable::*collidable_hit_function)(const collidable& ) const;
virtual ~collidable() = 0 {}
virtual collidable_hit_function get_hit_function() const = 0;
};
template<typename geometry_type>
class collidable_object : public collidable
{
public:
explicit collidable_object( geometry_type& geometry ) :
m_geometry( geometry )
{}
~collidable_object(){}
virtual collidable_hit_function get_hit_function() const
{
return static_cast<collidable_hit_function>( &collidable_object<geometry_type>::hit_function<geometry_type> );
}
template<typename rhs_geometry_type>
bool hit_function( const collidable& rhs ) const
{
return check_object_collision<geometry_type, rhs_geometry_type>( *this, rhs );
}
const geometry_type& geometry() const
{
return m_geometry;
}
private:
geometry_type& m_geometry;
};
bool check_collision( const collidable& lhs, const collidable& rhs )
{
collidable::collidable_hit_function hit_func = lhs.get_hit_function();
return (lhs.*hit_func)( rhs );
}
где функция check_object_collision
это шаблонная функция, которая тестирует на столкновение и была протестирована
Мой вопрос заключается в следующем: приведение в функцию get_hit_function
компилируется, но кажется подозрительным … я делаю что-то ужасно неправильное, что приведет к неопределенному поведению и множеству ночных кошмаров, или это нормально для приведения указателей на функции-члены шаблона из одного производного класса в другой.
меня смущает то, что в Visual C ++ 2012 это компилируется и кажется работать правильно …
Что могло заставить этот актерский состав пойти ужасно неправильно?
Я не очень понимаю, что означают указатели функции приведения …
В качестве дополнительного вопроса, будет ли способ реализовать это безопасным способом?
Можно привести указатель на метод из базового класса в производный класс. В противоположном направлении это очень плохая идея. Подумайте, что произойдет, если кто-то будет использовать ваш код следующим образом:
collidable_object<A> a;
collidable_hit_function f = a.get_hit_function();
collidable_object<B> b;
b.*f(...);
Yuor hit_function
(указано f
) будет ожидать this
быть collidable_object<A>
, но insted это получит collidable_object<B>
, Если эти два класса достаточно похожи, вы не получите ошибок, но ваш код, вероятно, уже делает что-то еще, чем следовало бы. Вы можете сделать это таким образом, если вам действительно нужно, но тогда вы должны позаботиться о том, чтобы вы использовали этот указатель только на нужном классе.
Что еще более важно, то, что вы делаете, скорее всего, концептуально неправильно. Если у вас есть два типа геометрии A
а также B
и вы проверяете на столкновение с
collidable_object<A> a;
collidable_object<B> b;
check_collision(a,b);
тогда то, что вы делаете, в конечном итоге называется:
check_object_collision<A, A>();
так что вы проверяете на столкновение, как будто оба collidable
с геометрии A
— Я предполагаю, что это не то, что вы хотите сделать.
Это проблема, которую вы, вероятно, не решите с помощью какой-либо одной языковой конструкции, так как она требует двухмерного массива различных проверок столкновений, по одной для каждой пары геометрии, и вам нужно стирание типа, чтобы иметь возможность манипулировать универсальным collidable
s.
Q: Можно ли приводить указатели на функции-члены шаблона из одного производного класса в другой?
A: Да, если get_hit_function () действительно совместима.
Если бы это был Java или C #, я бы просто объявил интерфейс 🙂
Да, static_cast
от bool (collidable_object<geometry_type>::*)() const
в bool (collidable::*)() const
прямо разрешено стандартом, потому что collidable
является доступным однозначным не виртуальным базовым классом collidable_object<geometry_type>
,
При преобразовании указателя на член в обратном направлении — из производного в базовый — static_cast
не требуется. Это потому, что существует действительное «стандартное преобразование» из bool (collidable::*)() const
в bool (collidable_object<geometry_type>::*)() const
,
[Conv.mem]Значение типа «указатель на член B типа cv T», где B — тип класса, может быть преобразовано в значение типа «указатель на член D типа cv T», где D — производный класс B. Если B — недоступный, неоднозначный или виртуальный базовый класс D, программа, для которой необходимо это преобразование, является плохо сформированной. Результат преобразования ссылается на тот же элемент, что и указатель на член до преобразования, но ссылается на элемент базового класса, как если бы он был членом производного класса. Результат относится к члену в экземпляре D Б. […]
Поскольку существует это действительное стандартное преобразование, и collidable
является доступным однозначным не виртуальным базовым классом collidable_object<geometry_type>
можно использовать static_cast
конвертировать из базы в производную.
[Expr.static.cast]Значение типа «указатель на член D типа cv1 T» может быть преобразовано в значение типа «указатель на член B типа cv2 T», где B — базовый класс D, если допустимое стандартное преобразование из «Указатель на член B типа T» на «указатель на член D типа T» существует, и cv2 — это та же квалификация cv, что и cv1 или более высокая квалификация, чем cv1. […] Если класс B содержит исходный член или является базовым или производным классом класса, содержащего исходный член, результирующий указатель на член указывает на исходный член. В противном случае результат приведения не определен. […]
Когда вы вызываете функцию-член производного класса через указатель на член-базу, вы должны убедиться, что объект базового класса, с которым вы ее вызываете, является экземпляром производного класса. В противном случае неопределенное поведение!
Вот рабочий пример. Раскомментирование последней строки main демонстрирует неопределенное поведение, которое, к сожалению, нарушает работу зрителя.
Что компилятор делает под капотом, чтобы заставить это работать? Это совсем другой вопрос;).
Что касается последующего вопроса, каноническим способом реализации двойной диспетчеризации является использование шаблона посетителя. Вот рабочий пример как это можно применить к вашему сценарию:
#include <iostream>
struct Geom
{
virtual void accept(Geom& visitor) = 0;
virtual void visit(struct GeomA&) = 0;
virtual void visit(struct GeomB&) = 0;
};
struct GeomA : Geom
{
void accept(Geom& visitor)
{
visitor.visit(*this);
}
void visit(GeomA& a)
{
std::cout << "a -> a" << std::endl;
}
void visit(GeomB& b)
{
std::cout << "a -> b" << std::endl;
}
};
struct GeomB : Geom
{
void accept(Geom& visitor)
{
visitor.visit(*this);
}
void visit(GeomA& a)
{
std::cout << "b -> a" << std::endl;
}
void visit(GeomB& b)
{
std::cout << "b -> b" << std::endl;
}
};
void collide(Geom& l, Geom& r)
{
l.accept(r);
}int main()
{
GeomA a;
GeomB b;
collide(a, a);
collide(a, b);
collide(b, a);
collide(b, b);
}