Это сложнее, чем просто решить вашу проблему. Вы выполняете ручную двойную отправку, и у вас есть ошибки. Мы можем исправить ваши ошибки.
Но проблема не в ошибках, а в том, что вы выполняете двойную отправку вручную.
Ручная двойная отправка подвержена ошибкам.
Каждый раз, когда вы добавляете новый тип, вам приходится писать O(N) нового кода, где N — количество существующих типов. Этот код основан на копировании и вставке, и если вы делаете ошибки, они молча продолжают неправильно отправлять некоторые угловые случаи.
Если вы продолжите выполнять двойную отправку вручную, у вас будут по-прежнему возникать ошибки всякий раз, когда вы или кто-либо еще изменяет код.
C++ не предоставляет собственного механизма двойной диспетчеризации. Но с c++17 мы можем автоматизировать его написание
Вот система, которая требует линейной работы для управления двойной отправкой плюс работа для каждого столкновения.
Для каждого типа в двойной отправке вы добавляете тип к pMapType
. Все, остальная часть рассылки автоматически пишется за вас. Затем наследуйте новый тип карты X
от collide_dispatcher<X>
.
Если вы хотите, чтобы у двух типов был код коллизии, напишите свободную функцию 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, но он становится намного более подробным и может потребовать повысить.
В то время как для определения того, что происходит, требуется линейный объем кода, компилятор сгенерирует квадратичный объем кода или статические табличные данные для реализации двойной диспетчеризации. Так что позаботьтесь о том, чтобы в вашей двойной диспетчерской таблице было более 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";
}
};
person
Yakk - Adam Nevraumont
schedule
24.05.2018