Как найти конкретный тип c производного компонента внутри вектора базовых компонентов? - PullRequest
0 голосов
/ 25 марта 2020

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

std::vector<std::unique_ptr<Component>> m_components;

Чтобы добавить к этому компоненту вектор, у меня есть метод Add () , который выглядит следующим образом:

template<class T>
    bool Add() {
        static_assert(std::is_base_of<Component, T>::value, " T must be derived from Component");
        if (!DuplicatesExist<T>()) {
            m_components.emplace_back(std::make_unique<T>(*this));
            return true;
        }
        return false;
    }

Поэтому, если бы я хотел добавить компонент Input к вектору компонентов, который является производным от Component, я сделал бы это:

 container.Add<Input>();

Итак, допустим, я хотел это Input Component, потому что я хотел бы использовать не производные методы внутри Input. Я хотел бы сделать что-то вроде этого: container.Get<Input>();

     template<class T>
            std::unique_ptr<T> &Get() {
                static_assert(std::is_base_of<Component, T>::value, " it have to be derived from component");
                auto& index = std::find_if(m_components.begin(), m_components.end(),
                                     [](auto &C) -> bool {
                                       // what to do here??
                                       if(std::is_same_v<T, C>)
                                           return true;
                                     });

                return index;
}

к сожалению, это невозможно: (

Есть ли способ сделать это?

Cheers

Ответы [ 2 ]

0 голосов
/ 25 марта 2020

Сначала у меня есть несколько замечаний:

 if (!DuplicatesExist<T>()) 

Что вы хотите проверить? Если ваш указатель является дубликатом, поскольку он указывает на тот же экземпляр? Если это так, вы можете напрямую использовать std::set вместо вектора!

 m_components.emplace_back(std::make_unique<T>(*this));

Это создаст новый объект как копию объекта "this". Таким образом, вы всегда будете получать новый объект и указатель, который содержит адрес того, который никогда не будет равен другому! Это ваше намерение?

И если в этом случае m_components является членом самого производного класса, хотим ли мы получить полную копию всех элементов? Здесь что-то очень загадочное: -)

ОК, но вернемся к вопросу:

Одно решение: используйте свой собственный «тег» ручной работы для каждого типа. Этот код немного велик и не очень удобен:

class Base
{   
    public:
        enum Id { ONE, TWO };
        virtual Id GetId() const = 0;
};  

class A: public Base
{   
    public:
    static constexpr Id id = ONE;
    Id GetId() const override { return id; }

    std::string someParm;

    void AFunc() { std::cout << "A-Func" << someParm << std::endl; }

    A( const std::string& parm ): someParm{ parm } {}
};  

class B: public Base
{   
    public:
    static constexpr Id id = TWO;
    Id GetId() const override { return id; }

    void BFunc() { std::cout << "B-Func" << std::endl; }
};  

template < typename T, typename CONTAINER >
T* Get( CONTAINER &objects )
{   
    auto it = std::find_if( objects.begin(), objects.end(), []( std::unique_ptr<Base>& ptr ) { return ptr->GetId() == T::id; } );

    if ( it != objects.end() ) return static_cast<T*>( it->get() );
    return nullptr;
}   


int main()
{
    std::vector< std::unique_ptr<Base> > objects;

    objects.emplace_back( std::make_unique<A> ("Hallo") );
    objects.emplace_back( std::make_unique<B> () );

    auto ptra = Get< A >( objects );
    if ( ptra ) ptra->AFunc();

    auto ptrb = Get< B >( objects );
    if ( ptrb ) ptrb->BFunc();
}

Или вы используете dynamic_cast, для которого требуется как минимум одна виртуальная функция в вашем базовом классе и включена поддержка RTTI для вашего компилятора, что обычно невозможно на маленькие встроенные устройства!

class Base
{   
    virtual void SomeFunc() {}; 
};  

class A: public Base
{   
    public:
        std::string someParm;
        void AFunc() { std::cout << "A-Func" << someParm << std::endl; }
        A( const std::string& parm ): someParm{ parm } {}
};  

class B: public Base
{   
    public:
        void BFunc() { std::cout << "B-Func" << std::endl; }
};  

    template < typename T, typename CONTAINER >
T* Get( CONTAINER &objects )
{   
    T* ret;
    for ( auto& ptr:  objects )
    {   
        Base* base = ptr.get();
        ret = dynamic_cast<T*>( base );
        if ( ret ) return ret;
    }   

    return nullptr; 
}


int main()
{
    std::vector< std::unique_ptr<Base> > objects;
    objects.emplace_back( std::make_unique<A> ("Hallo") );
    objects.emplace_back( std::make_unique<B> () );

    auto ptra = Get< A >( objects );
    if ( ptra ) ptra->AFunc();

    auto ptrb = Get< B >( objects );
    if ( ptrb ) ptrb->BFunc();
}   

или используйте std::variant

class A 
{   
    public:
        std::string someParm;
        void AFunc() { std::cout << "A-Func" << someParm << std::endl; }
        A( const std::string& parm ): someParm{ parm } {}
};  

class B
{   
    public:
        void BFunc() { std::cout << "B-Func" << std::endl; }
};

using VAR_T = std::variant< std::unique_ptr<A>, std::unique_ptr<B> >;

int main()
{   
    std::vector< VAR_T > objects;
    objects.emplace_back( std::make_unique<A> ("Hallo") );
    objects.emplace_back( std::make_unique<B> () );

    for ( auto& ptr: objects )
    {

        std::visit( []( auto& lptr )
                   {   
                       using T = std::decay_t<decltype(lptr)>;
                       if constexpr ( std::is_same< T, std::unique_ptr<A> >::value )
                       {
                           lptr->AFunc();
                       }
                       else if constexpr ( std::is_same< T, std::unique_ptr<B> >::value )
                       {
                           lptr->BFunc();
                       }
                   }
                   , ptr );
    }
}

Последний способ кажется мне самым простым. Это происходит без особых дополнительных затрат. Внутри варианта хранится только «тег», но, как вариант, содержащий только разные указатели одинакового размера, здесь нет никаких дополнительных затрат для размера варианта. И вы все еще можете использовать его на небольших встроенных устройствах, так как вам не нужен RTTI. Кроме того, вам не нужна виртуальная функция, и вам также не нужно наследовать от базового класса. Так что это очень гибкий!

0 голосов
/ 25 марта 2020

Ответ зависит от того, хотите ли вы самое простое решение или наиболее эффективное.

Самое простое будет вызывать dynamic_cast. Если предположить, что component имеет тип Component* и возможно получение T, dynamic_cast<T*>(component) вернет ненулевое значение T*, если это T *, или nullptr в противном случае.

Наиболее эффективный должен заменить ваш вектор на Boost.PolyCollection контейнер base_collection.

Есть несколько вариантов между ними. Можно было бы добавить поле или виртуальный метод в базовый класс, который бы возвращал различные значения для производных классов. Затем, если вы знаете, что компонент имеет тип T, вы используете static_cast указатель или ссылку, например static_cast<T&>(component). Вопреки интуитивному, такая ad-ho c реализация dynamic_cast может работать быстрее, чем generi c dynamic_cast<T*>(component).

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