Преобразование указателя для базового класса в унаследованный класс - PullRequest
3 голосов
/ 26 марта 2010

Я работаю над небольшой игрой в стиле roguelike, и для любого объекта / "вещи", не являющейся частью карты, используется класс 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, но создает циклические зависимости, которые неразрешимы.

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

Ответы [ 6 ]

7 голосов
/ 26 марта 2010

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

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

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

3 голосов
/ 26 марта 2010

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

// 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.
}

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

2 голосов
/ 26 марта 2010

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

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

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

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

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

Существует также «высший иерархический посетитель». Он знает, когда выйти.

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

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

1 голос
/ 26 марта 2010

Как уже отвечали, есть 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
}

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

1 голос
/ 26 марта 2010

Просто разыграйте:

XItem* Item = (XItem*)Ent;

В целом лучший подход заключается в следующем:

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

    // Code to link into inventory is here

    break;
}
1 голос
/ 26 марта 2010
XItem * Item = dynamic_cast< XItem * >( Ent );

if ( Item )
    // do something with item

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...