шаблонная двойная диспетчеризация с использованием указателей на функции

Я пытаюсь создать собственный движок столкновений для академических целей, и я застрял в общей проблеме программирования на С ++. У меня уже есть все геометрии, которые работают должным образом, и тест столкновения также работает должным образом.

Движок использует эти 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 это компилируется и кажется работать правильно …

Что могло заставить этот актерский состав пойти ужасно неправильно?

Я не очень понимаю, что означают указатели функции приведения …

В качестве дополнительного вопроса, будет ли способ реализовать это безопасным способом?

0

Решение

Можно привести указатель на метод из базового класса в производный класс. В противоположном направлении это очень плохая идея. Подумайте, что произойдет, если кто-то будет использовать ваш код следующим образом:

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 — Я предполагаю, что это не то, что вы хотите сделать.

Это проблема, которую вы, вероятно, не решите с помощью какой-либо одной языковой конструкции, так как она требует двухмерного массива различных проверок столкновений, по одной для каждой пары геометрии, и вам нужно стирание типа, чтобы иметь возможность манипулировать универсальным collidables.

1

Другие решения

Q: Можно ли приводить указатели на функции-члены шаблона из одного производного класса в другой?

A: Да, если get_hit_function () действительно совместима.

Если бы это был Java или C #, я бы просто объявил интерфейс 🙂

0

Да, 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);
}
0
По вопросам рекламы [email protected]