Написание метода получения компонента на С++ с использованием шаблонов

Я пытаюсь написать метод получения компонента в стиле 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;
};

Вы, вероятно, хотите dynamic_cast, но это предполагает, что Component полиморфен. Можете ли вы предоставить минимальный воспроизводимый пример, чтобы быть уверенным?

Angew is no longer proud of SO 10.04.2019 10:18

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

Figwig 10.04.2019 10:22

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

Aconcagua 10.04.2019 10:24

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

Angew is no longer proud of SO 10.04.2019 10:24

Dynamic Cast исправил это. Спасибо!

Figwig 10.04.2019 10:46

@Will Hain dynamic_cast может значительно снизить производительность, если GetComponent вызывается слишком много раз. Если вам нужен быстрый и постоянный запрос времени, я могу опубликовать решение, которое я использую для запросов компонентов.

Nishant Singh 10.04.2019 10:50
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
6
1 065
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

static_cast определенно не то, что вам нужно: это статический (время компиляции), поэтому он не может определить какую-либо информацию о времени выполнения.

Вместо этого вы хотите dynamic_cast. Обратите внимание, что у этого есть несколько требований, все из которых выполняются вашим кодом:

  • Классы должны быть полиморфными. Это закрыто, потому что у Component есть виртуальный деструктор.
  • Классы должны быть определены (а не просто объявлены) в момент приведения. Это тоже рассмотрено, потому что getComponent — это шаблон, и тип в приведении зависит от его параметров шаблона (на самом деле он один). Следовательно, определение должно быть видно только там, где создается экземпляр шаблона (т. е. там, где вызывается getComponent). Поскольку вы предположительно выполняете приведение типов для доступа к членам конкретного компонента, вы должны иметь видимым его определение, так что все в порядке.

@WillHain Будьте осторожны с производительностью / масштабируемостью, если вы надеетесь иметь много сущностей и / или компонентов и выполнять множество этих поисков в общих операциях. Существуют более эффективные способы структурирования ECS в зависимости от потребностей, но сложно дать прямой простой ответ.

Fire Lancer 10.04.2019 10:56

Существует некоторое фундаментальное недоразумение относительно 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!

Спасибо чувак. не выбрал, поскольку Ангью сказал об этом первым в комментарии, но ваше объяснение определенно улучшило мое понимание.

Figwig 10.04.2019 10:54

Что касается вашего примечания об указателях: ничто не мешает OP удерживать звездочку и вызывать функцию как getComponent<ConcreteComponent*>.

Angew is no longer proud of SO 10.04.2019 10:59

@Aconcagua @WillHain Я думаю, что type_index на основе unordered_map будет намного быстрее, чем решение на основе dynamic_cast, поскольку решение на основеdynamic_cast линейно проверяет вектор компонентов. Также решение dynamic_cast может дать сбой, если наши компоненты имеют иерархию вывода более 2, например Component->AnimationComponent->AdvancedAnimationComponent, а AdvancedAnimationComponent добавляется к вектору перед AnimationComponent, тогда getComponent<AnimationComponent*> неправильно даст AdvancedAnimationComponent

Nishant Singh 10.04.2019 11:15

@NishantSingh С другой стороны, индекс типа может быть проблемой, если мы ищем AnimationComponent, но на карте находится только AdvancedAnimationComponent. Ну, мы могли бы добавить объект AAC для обоих индексов (AC и AAC) для решения (или добавить объект AAC только для индекса AC, если мы хотим его скрыть).

Aconcagua 10.04.2019 11:27

@WillHain Вы должны выбрать только опубликованный ответ самый быстрый из нескольких, если все они одинаково ценны, в противном случае вам следует выбрать тот, который вы считаете ответом на свой вопрос Лучший. Что ж, в данном случае я не утверждаю, что мой делает это, список (покрытых) предварительных условий Ангью тоже хорош, так что ваш выбор в порядке, просто имейте в виду на будущее...

Aconcagua 10.04.2019 11:41

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