So I have designed a "type store" in C++ that can hold a single instance of any subclass of a defined type.
Here is an example of how it would be used:
class BaseClass { };
class FirstSubClass : public BaseClass { };
class SecondSubClass : public BaseClass { };
TypeStore<BaseClass> store;
// Each of these returns a pointer to the newly created instance
store.Create<BaseClass>();
store.Create<FirstSubClass>();
store.Create<SecondSubClass>();
// Each of these fails (returning nullptr) as an instance already exists for the type
store.Create<BaseClass>();
store.Create<FirstSubClass>();
store.Create<SecondSubClass>();
// Returns pointers to the instances for each type or nullptr if it is not present
store.Get<BaseClass>();
store.Get<FirstSubClass>();
store>Get<SecondSubClass>();
I have designed the class in a way that it handles all memory allocation and deallocation, making it easier to use.
If you are wondering what I would use this for, I am currently developing an Entity Component System that would use this for storing components; there can only be a single instance of each component type per entity.
There is one thing I don't really like about my implementation, and that is how I am forced to write the body of my methods in the header. I know this is required because I have a templated class, and I could extract them out into another file to be included by type_store.h
, however I still feel this is a rather messy solution.
And here is the source:
identifier.h
#ifndef IDENTIFIER_H
#define IDENTIFIER_H
#include <stddef.h>
typedef size_t Identifier;
#endif
type_identifier.h
#ifndef TYPE_IDENTIFIER_H
#define TYPE_IDENTIFIER_H
class TypeIdentifier {
public:
template<typename T>
static Identifier GetIdentifier() {
static Identifier identifier = nextIdentifier++;
return identifier;
}
private:
static Identifier nextIdentifier;
};
#endif
type_identifier.cpp
#include "type_identifier.h"
Identifier TypeIdentifier::nextIdentifier = 0;
type_store.h
#ifndef TYPE_STORE_H
#define TYPE_STORE_H
#include <map>
template<typename T>
class TypeStore {
public:
typedef std::map<Identifier, T *> Map;
typedef typename Map::iterator Iterator;
typedef typename Map::const_iterator ConstIterator;
~TypeStore() {
for (auto iterator = Begin(); iterator != End(); /* unused */) {
delete iterator->second;
store.erase(iterator++);
}
}
template<typename V>
bool Contains() {
static_assert(std::is_base_of<T, V>::value, "incorrect type for container");
return store.find(TypeIdentifier::GetIdentifier<T>()) != store.end();
}
template<typename V>
V *Get() {
static_assert(std::is_base_of<T, V>::value, "incorrect type for container");
if (!Contains<V>()) {
return nullptr;
}
Identifier identifier = TypeIdentifier::GetIdentifier<T>();
return static_cast<V *>(store.at(identifier));
}
template<typename V, typename ...A>
V *Create(A &&... args) {
static_assert(std::is_base_of<T, V>::value, "incorrect type for container");
if (Contains<V>()) {
return nullptr;
}
Identifier identifier = TypeIdentifier::GetIdentifier<T>();
V *instance = new V(std::forward(args)...);
if (!store.insert(std::make_pair(identifier, instance)).second){
delete instance;
return nullptr;
}
return instance;
}
template<typename V>
bool Remove() {
static_assert(std::is_base_of<T, V>::value, "incorrect type for container");
Identifier identifier = TypeIdentifier::GetIdentifier<T>();
delete Get<V>();
return store.erase(identifier) == 1;
}
typename Map::size_type Size() {
return store.size();
}
Iterator Begin() {
return store.begin();
}
Iterator End() {
return store.end();
}
ConstIterator Begin() const {
return store.begin();
}
ConstIterator End() const {
return store.end();
}
ConstIterator CBegin() const {
return store.cbegin();
}
ConstIterator CEnd() const {
return store.cend();
}
private:
Map store;
};
#endif