簡體   English   中英

創建一個容器來存儲模板類實例

[英]Creating a container to store Template Class instantiations

假設我有一個模板結構:

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'

所以我一直試圖讓它工作一段時間,我非常接近使用std::tuple作為我的EntityContainer類中的實體容器,但問題是我在訪問和改變特定元素時遇到了麻煩std::tuple在運行時,所以我認為它不適合這項工作。 也許這不是正確的方法,並且基於繼承的解決方案會更合適,但我想不出一個。

我試圖在提供足夠的代碼的同時盡可能清楚地說明這一點,我希望這不是要求太多,只是尋找一些方向。

基於繼承的解決方案會更合適,但我想不出一個。

使Entity成為常規類的子類並存儲指向基類的指針。

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

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

現在, EntityContainer可以存儲指向EntityBase指針,最好是智能指針。

從某種意義上說,您的問題類似於“如何將std::vector<int> s 和std::string s 和foo s 存儲在同一個容器中?”。 同一個模板的不同實例是不同的類型。 它們不一定有任何共同點(當然,除了是同一模板的實例化)。

簡短的回答是:你不能。 一個容器只能容納一種類型的元素。

但是,有些類型可以采用多種類型的值。 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 ,即您永遠不需要強制轉換或知道元素的動態類型是什么。

我在最近的一個項目中實現了類似的東西。 這是迄今為止我遇到的最常見的方式。 為此,我們需要有 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);

這種結構幾乎可以容納任何類型的物體。 這里唯一的缺點是您需要在編譯時知道類型。 但是仍然有很多方法可以擴展此結構以滿足您的需求。

異構容器實現:

#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 和指向某些數據的指針)。 可能對某人有用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM