I created a resource system in CPP:
template<typename T>
class Resource{
public:
Resource() {}
void push(std::shared_ptr<T> pRes) {
_items.emplace(_nextId , pRes);
}
friend class ResourceManager;
private:
size_t _nextId{ 0 };
std::unordered_map<size_t, std::shared_ptr<T>> _items;
};
class ResourceManager{
public:
ResourceManager() {}
~ResourceManager() {
for (auto& pair : _resources) delete pair.second;
}
template<typename T>
void register() {
Resource<T>* pRes = new Resource<T>;
_resources.emplace(typeid(T).name(), (void*)pRes);
}
template<typename T>
Resource<T>& getResource(){
return *(T*)_resources[typeid(T).name()];
}
private:
std::unordered_map<std::string, void*> _resources;
};
Now I want to add a method to ResourceManager
to modify all registered resources inside _resources
. How can I typecast void*
with stored typeid(T).name()
?
CodePudding user response:
You can't because the type needs to be known at compile time, and the keys of the map are not.
There are a few issues with your implementation: you're deleting a void*
in your destructor (which is UB), and your getResource
function returns a T
instead of a Resource<T>
.
A solution is to have some type erasure (via virtual functions):
class ErasedResource {
private:
ErasedResource() = default;
ErasedResource(ErasedResource&&) = default;
template<typename T>
friend class Resource;
friend class ResourceManager;
void push(std::shared_ptr<void> pRes) {
_items.emplace(_nextId , std::move(pRes));
}
size_t _nextId{ 0 };
// Store an "untyped" shared_ptr
std::unordered_map<size_t, std::shared_ptr<void>> _items;
public:
virtual ~ErasedResource() = default;
};
template<typename T>
class Resource : public ErasedResource {
public:
Resource() = default;
Resource(ErasedResource&&) = delete;
Resource(Resource&&) = default;
void push(std::shared_ptr<T> pRes) {
ErasedResource::push(std::static_pointer_cast<void>(std::move(pRes)));
}
friend class ResourceManager;
private:
std::shared_ptr<T> get(std::size_t index) const {
auto it = _items.find(index);
if (it == _items.end()) return nullptr;
return std::static_pointer_cast<T>(it->second);
}
};
class ResourceManager{
public:
ResourceManager() = default;
template<typename T>
void register() {
std::unique_ptr<ErasedResource> pRes(new Resource<T>);
_resources.emplace(std::type_index(typeid(T)), std::move(pRes));
}
template<typename T>
Resource<T>& getResource() const {
ErasedResource& res = *_resources.at(std::type_index(typeid(T)));
return static_cast<Resource<T>&>(res);
}
private:
// Switch to `type_index` (for performance)
// and `unique_ptr` (to avoid writing a destructor)
std::unordered_map<std::type_index, std::unique_ptr<ErasedResource>> _resources;
};
And if you want some function to operate on each resource that uses the type T
, add a virtual function to do it. For example, if you wanted to call "item.f()
" for each item in the resource, add a virtual void call_f() = 0
function to ErasedResource
and override it in Resource<T>
.