Reputation: 396
Disclaimer; I know this isn't pretty...
So, I'd like to have a registry or shared object storage for global, shared, instances of various different classes (sub systems, managers etc.) I don't want to use singletons for this and I'd like to be able to have a simple registry where I can register any instance against a key and then obtain a shared_ptr to that instance later on.
Through the use of dubious casting, raw-byte views, and underhand coercion I have managed to create a class that does this, and which works (with VS 2015 at least).
I can do things like
myRegistry->add<FooType>(1,2,3);
....
auto shared_foo_ptr = myRegistry->get<FooType>();
which is lovely, but of course the code under the hood smells.
Or does it?
Here it is, I'd really like some comments and constructive tearing-to-shreds;
class Registry {
struct reg_shared_ptr_item {
// raw byte storage for a shared_ptr of any type (assuming they are always the same size)
char _storage[sizeof(std::shared_ptr<reg_shared_ptr_item>)];
// the deleter will be bound to the correctly typed destructor of the original share_ptr
std::function<void(reg_shared_ptr_item&)> _deleter;
};
/**
this union is used to get access to the raw bytes of a pointer-to-member
since these can't be cast to anything directly
**/
union fptr_hasher {
char _raw[sizeof(&std::shared_ptr<Registry>::use_count)];
uint64_t _key_bits;
};
template<typename T>
static uint32_t get_key_for_type() {
// C++ disallows converting a pointer-to-member to a void* or any other normal pointer
// but we don't really need that, we just need the bits which will be unique since each type
// has it's own implementation
// NOTE: if the compiler for some reason or other chooses to be clever about instantiation and
// it doesn't actually create a new instance for each type then this will cause collisions
typedef long (std::shared_ptr<T>::*fptr_t)(void) const;
fptr_hasher hasher;
// create a pointer-to-member instance over the union so that we get access to the raw bytes
fptr_t* fptr = ::new(&hasher) fptr_t;
*fptr = &std::shared_ptr<T>::use_count;
return (reg_key_t)hasher._key_bits;
}
public:
typedef uint32_t reg_key_t;
Registry() = default;
~Registry() {
// clean up; invoke the type-bound destructors
for (auto& kv : _reg_map) {
kv.second._deleter(kv.second);
}
}
template<typename T, class...Args>
bool add(Args&&...args) {
auto key = get_key_for_type<T>();
auto found = _reg_map.find(key);
if (found == _reg_map.end()) {
// first; initialise the raw memory location to be a proper shared_ptr
reg_shared_ptr_item entry;
::new(entry._storage) std::shared_ptr<T>();
// create-assign a new shared_ptr instance
auto sas = reinterpret_cast<std::shared_ptr<T>*>(entry._storage);
*sas = std::make_shared<T>(args...); //< at this point the instance count is 1
// store off f-pointer to a properly typed destructor
entry._deleter = [](reg_shared_ptr_item& item) {
reinterpret_cast<std::shared_ptr<T>*>(item._storage)->~shared_ptr<T>();
};
_reg_map.emplace(key,entry);
return true;
}
return false;
}
template<typename T>
std::shared_ptr<T> get() const {
auto key = get_key_for_type<T>();
auto found = _reg_map.find(key);
if (found != _reg_map.end()) {
return *(reinterpret_cast<std::shared_ptr<T>*>(const_cast<char*>(found->second._storage)));
}
return nullptr;
}
template<typename T>
static reg_key_t getHash() {
return get_key_for_type<T>();
}
private:
typedef std::map<reg_key_t, reg_shared_ptr_item> reg_map_t;
reg_map_t _reg_map;
};
Upvotes: 1
Views: 875
Reputation: 52611
You could use std::type_index
as the key, and shared_ptr<void>
as the type-erased storage. Something along these lines perhaps (demo):
class Registry {
std::map<std::type_index, std::shared_ptr<void> > registry_;
public:
template<typename T, class...Args>
bool add(Args&&...args) {
std::type_index key(typeid(T));
if (!registry_.count(key)) {
auto p = std::make_shared<T>(std::forward<Args>(args)...);
registry_[key] = p;
return true;
}
return false;
}
template<typename T>
std::shared_ptr<T> get() const {
auto it = registry_.find(typeid(T));
if (it == registry_.end()) {
return std::shared_ptr<T>();
}
return std::static_pointer_cast<T>(it->second);
}
};
Upvotes: 2