Преобразование указателя базового класса в унаследованный класс

StackOverflow https://stackoverflow.com/questions/2523981

Вопрос

Я работаю над небольшой игрой-рогаликом, и любой объект/"вещь", не являющаяся частью карты, основан на классе XEntity.От него зависит несколько классов, например XPlayer, XItem и XMonster.

Моя проблема в том, что я хочу преобразовать указатель из XEntity в XItem, когда знаю, что объект находится в элементе.Пример кода, который я использую для подбора предмета, таков: это когда другой объект подбирает предмет, над которым он стоит.

void XEntity::PickupItem()
{
    XEntity *Ent = MapList; // Start of a linked list

    while(true)
    {
        if(Ent == NULL) { break; }

        if(Ent->Flags & ENT_ITEM)
        {
            Ent->RemoveEntity(); // Unlink from the map's linked list

            XItem *Item = Ent // Problem is here, type-safety

            // Code to link into inventory is here

            break;
        }

        Ent = Ent->MapList;
    }
}

Моей первой мыслью было создать в XEntity метод, который возвращает себя как указатель XItem, но создает неразрешимые циклические зависимости.

Я очень озадачен этим.Любая помощь очень ценится.

Это было полезно?

Решение

Если вы знать что XEntity действительно и XItem тогда вы можете использовать статический приведение.

XItem* Item = static_cast<XItem *>(Ent);

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

Другие советы

Кастинг решает проблему, как отмечали другие:

// dynamic_cast validates that the cast is possible. It requires RTTI 
// (runtime type identification) to work. It will return NULL if the 
// cast is not possible.
XItem* Item = dynamic_cast<XItem*>(Ent);
if(Item)
{
    // Do whatever you want with the Item.
}
else
{
    // Possibly error handling code as Ent is not an Item.
}

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

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

Отличный способ централизованно справиться с понижающим приведением — использовать шаблон Посетитель.Однако существует несколько форм посетителей: некоторые требуют понижения, некоторые — нет.С ациклическим посетителем, который требует понижающего приведения, легче работать, и, по моему опыту, он более мощный.

Другой посетитель, с которым я не пытался работать, утверждает, что обеспечивает ту же гибкость ациклического посетителя и скорость стандартного посетителя;это называется «кооперативный посетитель».Он по-прежнему выполняет приведение, просто делает это быстрее благодаря собственной таблице поиска.Причина, по которой я не пробовал кооперативного посетителя, заключается в том, что я не нашел способа заставить его работать на нескольких высших архиях... но я не потратил на это много времени, потому что застрял (в своем текущий проект) с ациклическим.

Самое интересное в кооперативном посетителе — это возвращаемые типы.Однако я использую своих посетителей, чтобы посещать целые блоки объектов и что-то делать с ними.Мне трудно представить, как будет работать возврат в этих случаях.

Стандартный посетитель выполняет понижающее приведение также с помощью механизма виртуального вызова, который быстрее, а иногда и безопаснее, чем явное приведение.Что мне не нравится в этом посетителе, так это то, что если вам нужно посетить WidgetX в высшей архии виджетов, вам также придется реализовать функциональность visit() для WidgetY и WidgetZ, даже если они вам не нужны.При больших и/или широких высших архиях это может быть PITA.Другие варианты этого не требуют.

Есть еще «высший гость».Он знает, когда остановиться.

Если вы не склонны использовать посетителя и хотите просто выполнить приведение, вы можете рассмотреть возможность использования функции boost::polymorphic_downcast.Он имеет механизмы безопасности и предупреждения динамического приведения с утверждениями в отладочных сборках и скорость статического приведения в выпуске.Хотя, возможно, в этом нет необходимости.Иногда ты просто знаешь, что делаешь правильный выбор.

Важная вещь, о которой вам нужно подумать и которой следует избегать, — это нарушение LSP.Если у вас много кода с «if (widget->type() == type1) { downcast...} else if (widget->type() == type2)...», то добавьте новые типы виджетов — это большая проблема, которая плохо влияет на большую часть кода.Ваш новый виджет на самом деле не будет виджетом, потому что все ваши клиенты слишком близки с вашим высшим руководством и не знают об этом.Шаблон посещений не устраняет эту проблему, но он централизует, что очень важно, когда у вас неприятный запах, и часто упрощает борьбу с ним.

Просто произнесите это:

XItem* Item = (XItem*)Ent;

В целом лучший подход:

if (XItem *Item = dynamic_cast<XItem*>(Ent)) {
    Ent->RemoveEntity();

    // Code to link into inventory is here

    break;
}
XItem * Item = dynamic_cast< XItem * >( Ent );

if ( Item )
    // do something with item

Чтобы это работало, вам нужно включить RTTI.Смотреть здесь Чтобы получить больше информации.

Как уже было сказано, есть 2 оператора:

XItem* Item = static_cast<XItem*>(Ent);

И:

XItem* Item = dynamic_cast<XItem*>(Ent);

второй медленнее, но безопаснее (он проверяет, возможно ли это) и может вернуть ноль, даже если Ent не является.

Я склонен использовать оба, завернутые в метод:

template <class T, class U>
T* my_cast(U* item)
{
#ifdef _NDEBUG_
  if (item) return &dynamic_cast<T&>(*item); // throw std::bad_cast
  else return 0;
#else
  return static_cast<T*>(item);
#endif
}

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top