класс — C ++ Проблемы проектирования: карта с различными абстрактными базовыми классами

Я сталкиваюсь с проблемами дизайна и могу сделать с некоторыми внешними входами. Я пытаюсь избежать абстрактного базового класса (поскольку я слышал, что это плохо).

Проблемы сводятся к этой структуре:

class entity... (base with pure virtual functions)
class hostile : public entity... (base with pure virtual functions)
class friendly : public entity... (base with pure virtual functions)
// Then further derived classes using these last base classes...

Первоначально я думал, что сойдет с рук:

const enum class FactionType : unsigned int
{ ... };

std::unordered_map<FactionType, std::vector<std::unique_ptr<CEntity>>> m_entitys;

И … я сделал, но это вызывает у меня проблемы, потому что мне нужен доступ к «уникальной» функции, скажем, враждебной или дружественной.

Я с позором постаралась (работал, но не нравится и не чувствует себя в безопасности):

// For-Each Loop: const auto& friendly : m_entitys[FactionType::FRIENDLY]
CFriendly* castFriendly = static_cast<CFriendly*>(&*friendly);

Я надеялся / пытался сохранить unordered_map дизайн, который использует FactionType в качестве ключа для базового абстрактного типа класса … В любом случае, ввод очень важен.

Если есть какие-либо синтаксические ошибки, прошу прощения.

0

Решение

О кастинге я согласен с @rufflewind. Броски означают разные вещи и полезны в разное время.

Для принудительной обработки области памяти во время компиляции (решение о типизации в любом случае происходит во время компиляции) используйте static_cast. Объем памяти на другом конце T *, равный sizeof (T), будет интерпретироваться как T независимо от правильного поведения.

Решения для dynamic_cast принимаются полностью во время выполнения, иногда требуя RTTI (информация о типе времени выполнения). Он принимает решение и возвращает либо нулевой указатель, либо действительный указатель на T, если это возможно.


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

Вы можете переместить приведение в класс сущности, сделать их правильно и просто оставить их в капсуле.

class entity
{
// Previous code
public:

// This will be overridden in hostiles to return a valid
// pointer and nullptr or 0 in other types of entities
virtual hostile* cast_to_hostile() = 0
virtual const hostile* cast_to_hostile() const = 0

// This will be overridden in friendlies to return a valid
// pointer and nullptr or 0 in other types of entities
virtual friendly* cast_to_friendly() = 0
virtual const friendly* cast_to_friendly() const = 0// The following helper methods are optional but
// can make it easier to write streamlined code in
// calling classes with a little trouble.

// Hostile and friendly can each implement this to return
// The appropriate enum member. This would useful for making
// decision about friendlies and hostiles
virtual FactionType entity_type() const = 0;

// These two method delegate storage of the knowledge
// of hostility or friendliness to the derived classes.
// These are implemented here as non-virtual functions
// because they shouldn't need to be overridden, but
// could be made virtual at the cost of a pointer
// indirection and sometimes, but not often a cache miss.
bool is_friendly() const
{
return entity_type() == FactionType_friendly;
}
bool is_hostile() const
{
return entity_type() == FactionType_hostile;
}
}

Эта стратегия хороша и плоха по разным причинам.

Плюсы:

Это концептуально просто. Это легко понять быстро, если вы понимаете полиморфизм.

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

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

Вы можете вернуть shared_ptr или другие пользовательские умные указатели вместо необработанных указателей. И вы, вероятно, должны.

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

Минусы:

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

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

Кто-то может подумать, что указатели этого возврата передают право собственности и удаляют их.

Каждый звонящий должен проверить правильность указателей перед использованием. Или вы можете добавить методы для проверки, но тогда вызывающие должны будут вызывать методы для проверки перед приведением. Такие проверки удивительны для пользователей класса и затрудняют правильное использование.

Это полиморфизм плотный. Это сбивает с толку людей, которым не по душе полиморфизм. Даже сегодня есть много людей, которым не нравится полиморфизм.

Возможен рефакторинг, который полностью исключает кастинг. Кастинг опасен и не является легким в использовании инструментом.

0

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