[英]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::any
或std::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.