Using C++ templating for SFML Resource Manager

2019-07-22 19:38发布

问题:

I'm thinking of two solutions to create a resource manager for my SFML-Game. Both require templating (I'm new to templating), but I prefer one of those solutions, even if I don't really know how to make it work.

I'd like to do something like this:

std::map<string, class> resources;

template<class T> T getResource(std::string name){
    return (T) resources.find(name);
}

In other words, I'd like to have all my resources stored in one class, and that there is only one function to get a resource, and this function should always return the type of the resource - So that, in order to load a resource, I can just do:

Texture texture = resourceManager.getResource("mytexture.png");

But I can't figure out how to get this working.

My second approach would be to template a class, and then instantiate multiple resource managers, one for each type of resource.

template<class Res> class ResourceManager {
    std::map<string, Res> resources;
    ...
    Res get(std::string name){
        return resources.find(name); //Just to visualize. Of course there would be null checks etc in the real method
    }
}

In my opinion, the second "solution" looks way cleaner code-wise, but the first one would be easier to use; And I could just make the methods static so I won't have to worry about decoupling.

I really don't know where I should put the instances of the managers from the second approach. Probably I would make the instances static and put them in my main class, but that just doesnt seem right.

回答1:

While nice, it will get really messy because you cannot have different resource types in the same map. They don't have a common base class. You don't have that many different resources though, so I'd suggest you just spell them out.

Personally, I hate strings as identifiers because the compiler cannot find typos, so I'm using an enum:

enum ResourceIdentifier
{
    // TEXTURES
    LoadingScreenBackground,
    FireAnimation,
    SmokeAnimation,
    FloorTile,
    // ...

    // FONTS
    MainFont
};

class ResourceManager
{
private:
    std::map<ResourceIdentifier, std::shared_ptr<sf::Texture>> m_Textures;
    std::map<ResourceIdentifier, std::shared_ptr<sf::Font>> m_Fonts;

public:
    std::shared_ptr<sf::Texture> LoadTexture(ResourceIdentifier id, const sf::String& file);
    std::shared_ptr<sf::Font> LoadFont(ResourceIdentifier id, const sf::String& file);

    std::shared_ptr<sf::Texture> GetTexture(ResourceIdentifier id) const;
    std::shared_ptr<sf::Font> GetFont(ResourceIdentifier id) const;
};


回答2:

The first approach won't work because you have to know the size of an object to store it in a container. This is why there is no such thing as a class type.

What you could do if you want to use your first approach, is to store void* pointers instead, whose size is known, and then have your function return this pointer casted to the right pointer type.

std::map<string, void*> resources;

template<class T> T* getResource(std::string name){
    return static_cast<T*>(resources.find(name));
}

This is kind of dirty and not type safe at all, it will probably break, but it works as you would expect. Your second approach is probably best :-)