Я пытаюсь написать метод получения компонента в стиле Unity. Это мой код до сих пор. Он компилируется, но возвращает первый найденный компонент, а не правильный. Я думаю, что неправильно использую static_cast. Как лучше это сделать? Примечание. Я не хочу жестко кодировать типы компонентов, я хочу иметь возможность скомпилировать этот движок и использовать все, что наследуется от Component, чтобы иметь возможность использовать эту систему. Также обратите внимание, что каждый компонент должен возвращаться как сам по себе, а не как компонент *, так как это скроет дочерние функции.
compStruct.components — это вектор компонента *s.
template <typename CompType>
inline CompType getComponent()
{
for(Component * currComp : compStruct.components)
{
if (static_cast<CompType>(currComp)!= nullptr)
{
return static_cast<CompType>(currComp);
}
}
return nullptr;
}
Вот пример универсального компонента
#pragma once
#include "Component.h"
#include "Animation2D.h"
class AnimationComponent : public Component
{
public:
AnimationComponent(GameObject*x) :Component(x) {}
~AnimationComponent() {}
void stepAnimation(double delta);
//add overload for 3d animations
int addAnimation(Animation2D);
void setAnimation(int);
private:
};
И базовый класс компонента:
#pragma once
class GameObject;
class Component
{
public:
Component(GameObject * h) { host = h; }
virtual ~Component() {}
GameObject* getHost() { return host; }
protected:
GameObject * host = nullptr;
};
Я уверен, что динамическое приведение требует включения класса, что в этом случае не сработает, поскольку я хочу иметь возможность использовать любой тип компонента (опять же, я думаю?). Я все равно включу пример MCV сейчас :)
Вам нужно привести к указателю, если вы хотите получить такой; static_cast
не возвращает нулевой указатель, если только приведенный указатель уже не был самим собой...
Поскольку мы говорим о функции шаблон, достаточно определить класс, когда шаблон равен созданный (т. е. при вызове функции). Я почти уверен, что у вас это будет покрыто кодом, использующим его.
Dynamic Cast исправил это. Спасибо!
@Will Hain dynamic_cast может значительно снизить производительность, если GetComponent вызывается слишком много раз. Если вам нужен быстрый и постоянный запрос времени, я могу опубликовать решение, которое я использую для запросов компонентов.
static_cast
определенно не то, что вам нужно: это статический (время компиляции), поэтому он не может определить какую-либо информацию о времени выполнения.
Вместо этого вы хотите dynamic_cast
. Обратите внимание, что у этого есть несколько требований, все из которых выполняются вашим кодом:
Component
есть виртуальный деструктор.getComponent
— это шаблон, и тип в приведении зависит от его параметров шаблона (на самом деле он один). Следовательно, определение должно быть видно только там, где создается экземпляр шаблона (т. е. там, где вызывается getComponent
). Поскольку вы предположительно выполняете приведение типов для доступа к членам конкретного компонента, вы должны иметь видимым его определение, так что все в порядке.@WillHain Будьте осторожны с производительностью / масштабируемостью, если вы надеетесь иметь много сущностей и / или компонентов и выполнять множество этих поисков в общих операциях. Существуют более эффективные способы структурирования ECS в зависимости от потребностей, но сложно дать прямой простой ответ.
Существует некоторое фундаментальное недоразумение относительно static_cast
: это будет просто делать приведения, и твой отвечает за то, чтобы приведённый указатель действительно указывал на объект целевого типа. static_cast
будет возвращать нулевой указатель только в том случае, если исходный указатель уже был самим собой, но никогда при несоответствии типов!
class B { /*...*/ };
class D1 : public B { };
class D2 : public B { };
D1 d1;
B* b = &d1;
D2* d2 = static_cast<D2*>(b);
d2
будет указателем на d1 (в некоторых случаях, связанных с множественным наследованием, может быть смещение), но интерпретируйте данные последнего совершенно по-разному (если только D1
и D2
не совместимы с макетом), и вы можете оказаться в аду!
Во-первых, лично я предпочитаю модифицированную подпись:
template <typename CompType>
inline CompType* getComponent();
// ^
Это позволяет вызывать вашу функцию как getComponent<SomeType>()
вместо getComponent<SomeType*>()
, кроме того, это позволяет использовать указатели внутри тела функции, что намного понятнее, см. Мой соответствующим образом скорректированный код ниже.
Тогда то, что вам действительно нужно, это dynamic_cast
(немного изменив ваш код в соответствии с моими личными предпочтениями...):
CompType* result = nullptr; // pointer: see above!
for(Component * currComp : compStruct.components)
{
result = dynamic_cast<CompType*>(currComp);
if (result)
break;
}
return result;
Обновлено: догоняю комментарий Ншант Сингх:
dynamic_cast
на самом деле довольно дорого.
Альтернативой может быть unordered_map
, заменяющий ваш вектор (пример того, как настроить, можно найти на type_index
документация; конечно, вы бы разместили свои объекты вместо строк...). Тогда ваш поиск может выглядеть так:
auto i = map.find(std::type_index(typeid(CompType));
return i == map.end() ? nullptr : static_cast<CompType*>(i->second);
// now, you CAN use static cast, as the map lookup provided you the
// necessary guarantee that the type of the pointee is correct!
Спасибо чувак. не выбрал, поскольку Ангью сказал об этом первым в комментарии, но ваше объяснение определенно улучшило мое понимание.
Что касается вашего примечания об указателях: ничто не мешает OP удерживать звездочку и вызывать функцию как getComponent<ConcreteComponent*>
.
@Aconcagua @WillHain Я думаю, что type_index
на основе unordered_map
будет намного быстрее, чем решение на основе dynamic_cast
, поскольку решение на основеdynamic_cast
линейно проверяет вектор компонентов. Также решение dynamic_cast
может дать сбой, если наши компоненты имеют иерархию вывода более 2, например Component->AnimationComponent->AdvancedAnimationComponent
, а AdvancedAnimationComponent
добавляется к вектору перед AnimationComponent
, тогда getComponent<AnimationComponent*>
неправильно даст AdvancedAnimationComponent
@NishantSingh С другой стороны, индекс типа может быть проблемой, если мы ищем AnimationComponent
, но на карте находится только AdvancedAnimationComponent
. Ну, мы могли бы добавить объект AAC для обоих индексов (AC и AAC) для решения (или добавить объект AAC только для индекса AC, если мы хотим его скрыть).
@WillHain Вы должны выбрать только опубликованный ответ самый быстрый из нескольких, если все они одинаково ценны, в противном случае вам следует выбрать тот, который вы считаете ответом на свой вопрос Лучший. Что ж, в данном случае я не утверждаю, что мой делает это, список (покрытых) предварительных условий Ангью тоже хорош, так что ваш выбор в порядке, просто имейте в виду на будущее...
Вы, вероятно, хотите
dynamic_cast
, но это предполагает, чтоComponent
полиморфен. Можете ли вы предоставить минимальный воспроизводимый пример, чтобы быть уверенным?