Есть ли «более чистый» способ найти первый экземпляр производного класса в векторе указателей на базу? - PullRequest
0 голосов
/ 21 февраля 2020

В моем игровом движке в настоящее время я пытаюсь упростить код:

std::vector<Base*> bases; //An arbitrary vector of the abstract class Base.

/*
GetFromBase
param derivedType: An integer used for quick comparison between each derived.
return: A pointer to the first derived found, or nullptr if not found.
*/

template <typename T>
T* GetFromBase(int derivedType)
{
  //Walk through each base
  for (Base* b : bases)
  {
    //Check if its internal type is the same as the enum
    if(b->GetDerivedType() == derivedType)
    {
      //Return the casted pointer
      return reinterpret_cast<T*>(b);
    }
  }

  //No derived of the desired type was found.
  return nullptr;
}

Возможно ли выполнить ту же функцию только с параметром шаблона, и если да, то сильно ли снижается производительность?

Ответы [ 2 ]

4 голосов
/ 21 февраля 2020

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

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

В качестве альтернативы вы можете получить тот же эффект, используя dynamic_cast, который в отличие от static_cast сам проверяет производный тип и возвращает нулевой указатель, если объект не имеет производного типа:

auto d = dynamic_cast<T*>(b);
if(d)
    return d;

В отличие от показанного подхода, dynamic_cast не требует тип наиболее производный должен быть равен T, просто в иерархии наследования самого производного объекта есть (уникальный, public) T.

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

2 голосов
/ 21 февраля 2020

Альтернативой является использование dynamic_cast

template <typename T>
T* GetFromBase(std::vector<Base *> &bases)
{
  for (auto b : bases)
  {
    // try to cast
    if(auto ptr = dynamic_cast<T *>(b); ptr)  // <- c++17 syntax
    {
      return ptr;
    }
  }

  return nullptr;
}

Также более короткая версия с использованием std :: find_if

template <typename T>
T* GetFromBase(std::vector<Base *> &bases)
{
    auto it = std::find_if(begin(bases), end(bases), [](Base *b) {
         return dynamic_cast<T *>(b);
    });
    return it == end(bases) ? nullptr : dynamic_cast<T *>(it);
}
...