Это сложнее, чем просто решить вашу проблему.Вы делаете двойную ручную отправку, и у вас есть ошибки.Мы можем исправить ваши ошибки.
Но проблема у вас не в ваших ошибках, а в том, что вы делаете ручную двойную отправку.
Ручная двойная отправка подвержена ошибкам.
Каждый раз, когда вы добавляете новый тип, вы должны написать O (N) новый код, где N - количество существующих типов.Этот код основан на копировании и вставке, и если вы делаете ошибки, они молча продолжают неправильно отправлять некоторые угловые случаи.
Если вы продолжите выполнять двойную ручную диспетчеризацию, вы будете иметь ошибки каждый раз, когда вы или кто-либо другойelse изменяет код.
C ++ не предоставляет своего собственного механизма двойной отправки.Но с помощью c ++ 17 мы можем автоматизировать написание этого
Вот система, которая требует линейной работы для управления двойной диспетчеризацией плюс работа для каждого столкновения.
Для каждого типа в двойной отправке вы добавляете тип к pMapType
.Вот и все, остальная часть рассылки написана для вас автоматически.Затем наследуйте ваш новый тип карты X
от collide_dispatcher<X>
.
Если вы хотите, чтобы два типа имели код столкновения, напишите free function do_collide(A&,B&)
.Проще в варианте pMapType
должно быть A
.Эта функция должна быть определена до того, как A
и B
определены для работы диспетчера.
Этот код запускается, если выполняется либо a.collide(b)
, либо b.collide(a)
, где A
и B
- это динамические типы a
и b
соответственно.
Вы также можете сделать do_collide
другом того или другого типа.
Без дальнейших церемоний:
struct Player;
struct Wall;
struct Monster;
using pMapType = std::variant<Player*, Wall*, Monster*>;
namespace helper {
template<std::size_t I, class T, class V>
constexpr std::size_t index_in_variant() {
if constexpr (std::is_same<T, std::variant_alternative_t<I, V>>{})
return I;
else
return index_in_variant<I+1, T, V>();
}
}
template<class T, class V>
constexpr std::size_t index_in_variant() {
return helper::index_in_variant<0, T, V>();
}
template<class Lhs, class Rhs>
constexpr bool type_order() {
return index_in_variant<Lhs*, pMapType>() < index_in_variant<Rhs*, pMapType>();
}
template<class Lhs, class Rhs>
void do_collide( Lhs&, Rhs& ) {
std::cout << "Nothing happens\n";
}
struct MapObject;
template<class D, class Base=MapObject>
struct collide_dispatcher;
struct MapObject {
virtual void collide( MapObject& ) = 0;
protected:
template<class D, class Base>
friend struct collide_dispatcher;
virtual void collide_from( pMapType ) = 0;
virtual ~MapObject() {}
};
template<class D, class Base>
struct collide_dispatcher:Base {
D* self() { return static_cast<D*>(this); }
virtual void collide( MapObject& o ) final override {
o.collide_from( self() );
}
virtual void collide_from( std::variant<Player*, Wall*, Monster*> o_var ) final override {
std::visit( [&](auto* o){
using O = std::decay_t< decltype(*o) >;
if constexpr( type_order<D,O>() ) {
do_collide( *self(), *o );
} else {
do_collide( *o, *self() );
}
}, o_var );
}
};
void do_collide( Player& lhs, Wall& rhs );
void do_collide( Player& lhs, Monster& rhs );
struct Player : collide_dispatcher<Player> {
friend void do_collide( Player& lhs, Wall& rhs ) {
std::cout << "Player hit a Wall\n";
}
friend void do_collide( Player& lhs, Monster& rhs ) {
std::cout << "Player fought a Monster\n";
}
};
void do_collide( Wall& lhs, Monster& rhs );
struct Wall : collide_dispatcher<Wall> {
friend void do_collide( Wall& lhs, Monster& rhs ) {
std::cout << "Wall blocked a Monster\n";
}
};
void do_collide( Monster& lhs, Monster& rhs );
struct Monster : collide_dispatcher<Monster> {
friend void do_collide( Monster& lhs, Monster& rhs ) {
std::cout << "Monster Match!\n";
}
};
Живой пример .
Несмотря на то, что сантехника здесь сложная, это означает, что вы не выполняете никакой двойной диспетчеризации вручную.Вы просто пишете конечные точки.Это уменьшает количество мест, в которых вы можете иметь опечатки в угловом регистре.
Тестовый код:
int main() {
MapObject* pPlayer = new Player();
MapObject* pWall = new Wall();
MapObject* pMonster = new Monster();
std::cout << "Player:\n";
pPlayer->collide(*pPlayer);
pPlayer->collide(*pWall);
pPlayer->collide(*pMonster);
std::cout << "Wall:\n";
pWall->collide(*pPlayer);
pWall->collide(*pWall);
pWall->collide(*pMonster);
std::cout << "Monster:\n";
pMonster->collide(*pPlayer);
pMonster->collide(*pWall);
pMonster->collide(*pMonster);
}
Вывод:
Player:
Nothing happens
Player hit a Wall
Player fought a Monster
Wall:
Player hit a Wall
Nothing happens
Wall blocked a Monster
Monster:
Player fought a Monster
Wall blocked a Monster
Monster Match!
Вы также можете создать центральную typedef для std::variant<Player*, Wall*, Monster*>
и сделать так, чтобы map_type_index
использовал этот центральный typedef для определения его порядка, сократив работу по добавлению нового типа в систему двойной диспетчеризации до добавления типа в одном месте, реализуя новыйвведите и отправьте код столкновения, который должен что-то делать.
Более того, этот код двойной отправки можно сделать наследуемым;производный тип от Wall
может отправлять на Wall
перегрузки.Если вы хотите это, вы должны сделать перегрузки метода collide_dispatcher
не final
, что позволит SpecialWall
перегрузить их.
Это c ++ 17 , но в текущих версияхкаждого крупного компилятора теперь поддерживает то, что ему нужно.Все может быть сделано в c ++ 14 или даже c ++ 11 , но это становится намного более многословным и может потребовать boost .
Хотя для определения того, что происходит, требуется линейный объем кода, компилятор сгенерирует квадратичный объем кода или данных статической таблицы для реализации двойной диспетчеризации.Поэтому будьте осторожны, прежде чем в вашей таблице двойной отправки будет более 10 000+ типов.
Если вы хотите, чтобы MapObject
был конкретным, отделите от него интерфейс, удалите final
из диспетчера и добавьте MapObject
до pMapType
struct Player;
struct Wall;
struct Monster;
struct MapObject;
using pMapType = std::variant<MapObject*, Player*, Wall*, Monster*>;
namespace helper {
template<std::size_t I, class T, class V>
constexpr std::size_t index_in_variant() {
if constexpr (std::is_same<T, std::variant_alternative_t<I, V>>{})
return I;
else
return index_in_variant<I+1, T, V>();
}
}
template<class T, class V>
constexpr std::size_t index_in_variant() {
return helper::index_in_variant<0, T, V>();
}
template<class Lhs, class Rhs>
constexpr bool type_order() {
return index_in_variant<Lhs*, pMapType>() < index_in_variant<Rhs*, pMapType>();
}
template<class Lhs, class Rhs>
void do_collide( Lhs&, Rhs& ) {
std::cout << "Nothing happens\n";
}
struct collide_interface;
template<class D, class Base=collide_interface>
struct collide_dispatcher;
struct collide_interface {
virtual void collide( collide_interface& ) = 0;
protected:
template<class D, class Base>
friend struct collide_dispatcher;
virtual void collide_from( pMapType ) = 0;
virtual ~collide_interface() {}
};
template<class D, class Base>
struct collide_dispatcher:Base {
D* self() { return static_cast<D*>(this); }
virtual void collide( collide_interface& o ) override {
o.collide_from( self() );
}
virtual void collide_from( pMapType o_var ) override {
std::visit( [&](auto* o){
using O = std::decay_t< decltype(*o) >;
if constexpr( type_order<D,O>() ) {
do_collide( *self(), *o );
} else {
do_collide( *o, *self() );
}
}, o_var );
}
};
struct MapObject:collide_dispatcher<MapObject>
{
/* nothing */
};
живой пример .
при желании Player
спуститься с MapObject
вы должны использовать Base
аргумент collide_dispatcher
:
void do_collide( Player& lhs, Wall& rhs );
void do_collide( Player& lhs, Monster& rhs );
struct Player : collide_dispatcher<Player, MapObject> {
friend void do_collide( Player& lhs, Wall& rhs ) {
std::cout << "Player hit a Wall\n";
}
friend void do_collide( Player& lhs, Monster& rhs ) {
std::cout << "Player fought a Monster\n";
}
};
void do_collide( Wall& lhs, Monster& rhs );
struct Wall : collide_dispatcher<Wall, MapObject> {
friend void do_collide( Wall& lhs, Monster& rhs ) {
std::cout << "Wall blocked a Monster\n";
}
};
void do_collide( Monster& lhs, Monster& rhs );
struct Monster : collide_dispatcher<Monster, MapObject> {
friend void do_collide( Monster& lhs, Monster& rhs ) {
std::cout << "Monster Match!\n";
}
};