Home > OS >  How to do typecast with prestored typeid(T) during runtime?
How to do typecast with prestored typeid(T) during runtime?

Time:06-13

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>.

  •  Tags:  
  • c
  • Related