【问题标题】:Creating a container to store Template Class instantiations创建一个容器来存储模板类实例
【发布时间】:2020-09-18 18:35:53
【问题描述】:

假设我有一个模板结构:

template<typename T>
struct Entity
{
  Entity(int Id) : id(Id) 
  { 
    /* init 'data' */ 
  }
  
  T* data; 
  int id; 
};

第二个类的作用是存储不同类型的实体:

typename<Ts...>
class EntityContainer
{
public:
  EntityContainer(Ts... Args)
  {
     /* store 'Args' (which are all template instantiations of entity) in some 'entity container' */
  }

  template<typename T>
  void addNewEntity(T&& entity)
  {
     /* new entities can be added to the 'entity container' at run time */
  }
  
  template<typename T>
  T& getEntityById(int id)
  {
     /* stored Entities must be accessible and mutable at run time */
  }
private:
  /*Entity container goes here e.g. std::tuple<Ts...> */
};

这两个类将串联使用,如下所示:

//create two entities of differing types
Entity<A> myFirstEntity(1);     
Entity<B> mySecondEntity(2);          

//store the entities in a container
EntityContainer<Entity<A>, Entity<B>> myContainer(myFirstEntity, mySecondEntity);

//create a new Entity and add it to the container
Entity<C> myThirdEntity(3);
mycontainer.addNewEntity(myThirdEntity);

//retrieve entity from container at run time
myContainer<Entity<B>>.getEntityById(2);          //should return 'mySecondEntity'

所以我一直试图让它工作一段时间,我在我的EntityContainer 类中使用std::tuple 作为实体容器非常接近,但问题是我在访问和变异时遇到了问题std::tuple 在运行时的特定元素,所以我认为它不适合这项工作。也许这不是正确的方法,基于继承的解决方案会更合适,但我想不出一个。

在提供足够代码的同时,我已经尽力让这一点尽可能清晰,我希望这不是要求太多,只是寻找一些方向。

【问题讨论】:

  • 不是让它听起来比它更简单,但是 - 当你去解释真正的问题时,你应该为它留出空间。现在,您的问题描述几乎没有空间。它的底部很拥挤......你能把它变成一个签名问题吗?
  • 这看起来像一个复杂版本的实体组件系统,你想要一个非模板化的实体,通常只是一个 id 和一些标识附加到这个实体的组件的方式,那么你有您可以附加到实体以创建不同类型的不同类型的组件(这里是您的数据)。您希望实体直接存储在容器中,而不是通过指针,因此在迭代时您不会像其他建议那样具有间接性
  • 是的,但是如何将组件“附加”到您的实体?当然,在某些时候,您需要一个容器,该容器由包含不同类型组件的实体拥有
  • 组件存储在另一个容器中,例如实体只有一个索引到该容器中

标签: c++ templates containers


【解决方案1】:

基于继承的解决方案会更贴切,但我想不出一个。

Entity 设为常规类的子类并存储指向基类的指针。

struct EntityBase
{
   virtual ~EntityBase() {}
};

template<typename T>
struct Entity : EntityBase
{
   ...
};

现在,EntityContainer 可以存储指向EntityBase 的指针,最好是智能指针。

【讨论】:

    【解决方案2】:

    从某种意义上说,您的问题类似于“如何将std::vector&lt;int&gt;s 和std::strings 和foos 存储在同一个容器中?”。同一模板的不同实例是不同的类型。它们不一定有任何共同点(当然除了是同一模板的实例)。

    简短的回答是:你不能。一个容器只能容纳一种类型的元素。

    但是,有些类型可以采用多种类型的值。有std::anystd::variant。阅读更多内容的关键字是“类型擦除”。

    也许您知道运行时多态形式的类型擦除,这可能是最容易掌握的化身。您无需将实际类型存储在容器中,而是将指针(最好是智能指针)存储到容器中的基类。

    我只会概述方法:

    struct EntityBase {
        // declare interface here
        virtual ~EntityBase() {}
    };
    
    template <typename T>
    struct Entity : EntityBase {
        T* data;
    };
    
    std::vector<std::shared_ptr<EntityBase>> entities;
    

    最简单的情况是接口不依赖于T,即您永远不需要转换或知道元素的动态类型是什么。

    【讨论】:

    • 好吧,看起来很有希望,当您在EntityBase 中有一个方法需要访问Entity 中的data 时,问题就来了,对吗?例如带有签名的方法:virtual T* getData() ... 显然T 对这个函数没有任何意义。有什么办法吗?
    • @jf192210 不,您会将与模板参数无关的所有内容都放在基类中,仅此而已。看看std::variantstd::visit 然后,不幸的是我不能告诉你太多。虽然经常有人问类似的问题,但如果您搜索一下,您应该会找到更多答案
    • 没问题,你把我引向了正确的方向,这就是我所能要求的:)
    • @jf192210 这看起来是一个不错的介绍。我没读过,但似乎很详尽gieseanw.wordpress.com/2017/05/03/…
    • @jf192210 btw std::tuple 是一种产品类型。值集是类型的可能值集的乘积,您更需要所谓的 sum 类型。可能值的总和类型集是各个类型的所有可能值的并集。希望这有任何意义。我的意思是std::tuple&lt;int,double&gt; 包含一个字符串和一个双精度,std::variant&lt;int,double&gt; 包含一个intdouble,你想要后者
    【解决方案3】:

    我在最近的一个项目中实现了类似的东西。这是迄今为止我遇到的最常见的方式。 为此,我们需要有 3 个基本对象,

    /**
     * This class will be used as the base class of the actual container.
     */
    class ContainerBase {
    public:
        ContainerBase() {}
        virtual ~ContainerBase() {}
    };
    
    /**
     * This class will hold the actual data.
     */
    template<class TYPE>
    class Container : public ContainerBase {
    public:
        Container() {}
        ~Container() {}
    
        std::vector<TYPE> data;
    };
    
    /**
     * This is the class which is capable of holding all the types of objects with different types.
     * It can store multiple objects of the same time since we are using a dynamic array (std::vector<>).
     */
    class EntityContainer {
    public:
        EntityContainer() {}
        ~EntityContainer() {}
    
    private:
        std::vector<std::string> registeredTypes;
        std::unordered_map<std::string, ContainerBase*> containerMap;
    };
    

    然后我们必须定义几个方法来添加数据。但是为了给容器添加数据,我们必须先注册它。

    class EntityContainer {
    public:
        EntityContainer() {}
        ~EntityContainer()
        {
            // Make sure to delete all the allocated memory!
            for (auto containerPair : containerMap)
                delete containerPair.second;
        }
    
        /**
         * Check is the required object type is registered by iterating through the registered types.
         */
        template<class TYPE>
        bool isRegistered()
        {
            for (auto& type : registeredTypes)
                if (type == typeid(TYPE).name())
                    return true;
    
            return false;
        }
    
        /**
         * Register a new type.
         */
        template<class TYPE>
        void registerType()
        {
            // Return if the type is already available.
            if (isRegistered<TYPE>())
                return;
    
            std::string type = typeid(TYPE).name();
    
            containerMap[type] = new Container<TYPE>;
            registeredTypes.push_back(type);
        }
    
        /**
         * Get the container of the required type.
         * This method returns an empty container if the required type is not registered.
         */
        template<class TYPE>
        Container<TYPE>* getContainer()
        {
            if (!isRegistered<TYPE>())
                registerType<TYPE>();
    
            return dynamic_cast<Container<TYPE>*>(containerMap[typeid(TYPE).name()]);
        }
    
        /**
         * Add a new entity to the TYPE container.
         */
        template<class TYPE>
        void addNewEntity(TYPE&& data)
        {
            getContainer<TYPE>()->data.push_back(std::move(data));
        }
    
        /**
         * Get a data which is stored in the container of the required type using its ID.
         */
        template<class TYPE>
        TYPE getEntityByID(int ID)
        {
            std::vector<TYPE> tempVec = getContainer<TYPE>()->data;
    
            for (auto entity : tempVec)
                if (entity.id == ID)
                    return entity;
    
            return TYPE();
        }
            getContainer<TYPE>()->data.push_back(data);
        }
    
        /**
         * Get a data which is stored in the container of the required type using its index.
         */
        template<class TYPE>
        TYPE getData(size_t index)
        {
            return getContainer<TYPE>()->data[index];
        }
    
    private:
        std::vector<std::string> registeredTypes;
        std::unordered_map<std::string, ContainerBase*> containerMap;
    };
    

    现在使用上面的数据结构,你就可以完成你的任务了。

    //store the entities in a container
    EntityContainer container;
    
    Entity<A> myFirstEntity(1);     
    Entity<B> mySecondEntity(2);          
    Entity<C> myThirdEntity(3);
    
    mycontainer.addNewEntity(myFirstEntity);
    mycontainer.addNewEntity(mySecondEntity);
    mycontainer.addNewEntity(myThirdEntity);
    
    mycontainer.getEntityByID<Entity<C>>(3);
    

    这种结构几乎可以容纳任何类型的对象。这里唯一的缺点是您需要在编译时知道类型。但仍有很多方法可以扩展此结构以满足您的需求。

    【讨论】:

    • 你给了我很多东西,谢谢 :) 看起来很有希望
    • @jf192210 随时 ;)
    【解决方案4】:

    异构容器实现:

    #include <vector>
    #include <unordered_map>
    #include <functional>
    #include <iostream>
    #include<experimental/type_traits>
    
    
    // ### heterogeneous container ###
    
    namespace container
    {
    template<class...>
    struct type_list{};
    
    template<class... TYPES>
    struct visitor_base
    {
        using types = container::type_list<TYPES...>;
    };
    
    struct entity_container
    {
    public:
        entity_container() = default;
    
        template<typename ...Ts>
        entity_container(Ts ...args)
        {
            auto loop = [&](auto& arg)
            {
                this->push_back(arg);
            };
            (loop(args), ...);
        }
    
        entity_container(const entity_container& _other)
        {
            *this = _other;
        }
    
        entity_container& operator=(const entity_container& _other)
        {
            clear();
            clear_functions = _other.clear_functions;
            copy_functions = _other.copy_functions;
            size_functions = _other.size_functions;
            stored_ids_functions = _other.stored_ids_functions; 
            id_delete_functions = _other.id_delete_functions;
            
            for (auto&& copy_function : copy_functions)
            {
                copy_function(_other, *this);
            }
            return *this;
        }
    
        template<class T>
        void push_back(const T& _t)
        {
            //is a new container being made? if so ...
            if (items<T>.find(this) == std::end(items<T>)) 
            {
                clear_functions.emplace_back([](entity_container& _c){items<T>.erase(&_c);});
                id_delete_functions.emplace_back([&](entity_container& _c, int id)
                {
                    for(auto& ent : items<T>[&_c])
                    {
                        if(ent.getId() == id)
                            items<T>[&_c].erase( std::find(items<T>[&_c].begin(), items<T>[&_c].end(), ent) );
                    }   
                });
                copy_functions.emplace_back([](const entity_container& _from, entity_container& _to)
                {
                    items<T>[&_to] = items<T>[&_from];
                });
                size_functions.emplace_back([](const entity_container& _c){return items<T>[&_c].size();}); //returns the size of the vector
                stored_ids_functions.emplace_back([](const entity_container& _c)
                {
                    std::vector<int> stored_ids; 
                    for(auto& ent : items<T>[&_c])
                        stored_ids.push_back(ent.getId());
                    return stored_ids;
                });
            }
            items<T>[this].push_back(_t);
        }
    
        void clear()
        {
            for (auto&& clear_func : clear_functions)
            {
                clear_func(*this);
            }
        }
    
        template<class T>
        size_t number_of() const
        {
            auto iter = items<T>.find(this);
            if (iter != items<T>.cend())
                return items<T>[this].size();
            return 0;
        }
    
        size_t size() const
        {
            size_t sum = 0;
            for (auto&& size_func : size_functions)
                sum += size_func(*this);
            // gotta be careful about this overflowing
            return sum;
        }
    
        bool exists_by_id(int id) const
        {
            for(auto&& id_func : stored_ids_functions) //visit each of the id functions
            {
                std::vector<int> ids = id_func(*this);
                auto res = std::find(std::begin(ids), std::end(ids), id);
                if(res != std::end(ids))
                    return true;
            }
            return false;
        } 
    
        void delete_by_id(int id)
        {
            for(auto&& id_delete_func : id_delete_functions)
                id_delete_func(*this, id);
        }
    
        void print_stored_ids()
        {
            for(auto&& id_func : stored_ids_functions) //visit each of the id functions
            {
                std::vector<int> ids = id_func(*this);
                for(auto& id : ids)
                    std::cout << "id: " << id << std::endl;
            }
        }
        ~entity_container()
        {
            clear();
        }
    
        template<class T>
        void visit(T&& visitor)
        {
            visit_impl(visitor, typename std::decay_t<T>::types{});
        }
    
    private:
        template<class T>
        static std::unordered_map<const entity_container*, std::vector<T>> items;
    
        template<class T, class U>
        using visit_function = decltype(std::declval<T>().operator()(std::declval<U&>()));
    
        template<class T, class U>
        static constexpr bool has_visit_v = std::experimental::is_detected<visit_function, T, U>::value;
    
        template<class T, template<class...> class TLIST, class... TYPES>
        void visit_impl(T&& visitor, TLIST<TYPES...>)
        {
            (..., visit_impl_help<std::decay_t<T>, TYPES>(visitor));
        }
    
        template<class T, class U>
        void visit_impl_help(T& visitor)
        {
            static_assert(has_visit_v<T, U>, "Visitors must provide a visit function accepting a reference for each type");
            for (auto&& element : items<U>[this])
            {
                visitor(element);
            }
        }
        std::vector<std::function<void(entity_container&)>> clear_functions;
        std::vector<std::function<void(const entity_container&, entity_container&)>> copy_functions;
        std::vector<std::function<size_t(const entity_container&)>> size_functions;
        std::vector<std::function<std::vector<int>(const entity_container&)>> stored_ids_functions;
        std::vector<std::function<void(entity_container&, int)>> id_delete_functions;
    };
    
    template<class T>
    std::unordered_map<const entity_container*, std::vector<T>> entity_container::items;
    }
    

    用途:

    template<class T>
    struct Entity
    {
    public:
        Entity(int Id) : id(Id) 
        {
            data = new T();
        }
    
        bool operator==(const Entity<T>& rhs) const {
            return (this->id == rhs.id && this->data == rhs.data);
        }
    
        int getId(){return id;}
        T*& getData(){ return data; }
    
    private:
        int id;
        T* data;
    };
    
    struct A{};
    struct B{};
    struct C{};
    
     /* ## main ## */
    
    
    int main()
    {
        
        Entity<A> entity_a(1);
        Entity<B> entity_b(2);
        Entity<C> entity_c(3);
        Entity<C> entity_d(10);
    
        container::entity_container c(entity_a, entity_b, entity_c);
        c.push_back(entity_d);
    
        c.print_stored_ids(); //prints the id's of all store entities
    
        c.delete_by_id(3);    //deletes the store entities with the parsed id
        c.delete_by_id(1);
        c.delete_by_id(10);
    
        c.print_stored_ids();
    }
    

    大部分来自https://gieseanw.wordpress.com/2017/05/03/a-true-heterogeneous-container-in-c/,并添加了一些个人添加以适应类型的容器,例如Entity(即带有ID和指向某些数据的指针)。可能对某人有用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-04-30
      • 2023-02-26
      • 1970-01-01
      • 2022-07-21
      • 1970-01-01
      • 1970-01-01
      • 2021-01-04
      相关资源
      最近更新 更多