Pretty much every book I've read and every forum topic I've seen which discusses the Singleton pattern, and global variables in general, basically sum up as: don't do it. I understand the arguments against this too, but I've come up against a situation in something I'm working on where I think it may be the exception, although I'm not really sure. I'm hoping some of the experienced programmers here might be able to provide some perspective on the situation. First of all let me explain a little about what I'm doing / trying to do. Currently I have some basic texture resource management in this simple class:
#ifndef TEXTURE_MANAGER_H_
#define TEXTURE_MANAGER_H_
#include <Game/Common.h>
#include <memory>
namespace Game
{
class TextureManager
{
public:
static const std::shared_ptr<sf::Texture> GetTexture(std::string path = "");
private:
static std::map<std::string, std::shared_ptr<sf::Texture> > m_textures;
};
};
#endif //TEXTURE_MANAGER_H_
and
#include <Game/TextureManager.h>
const std::shared_ptr<sf::Texture> Game::TextureManager::GetTexture(std::string path)
{
//see if we already have it loaded first and return if we do
if(!path.empty())
{
for(auto texture : m_textures)
{
if(texture.first == path)
{
std::cout << "Texture manager found: " << path << std::endl;
return texture.second;
}
}
}
//else attempt to load from path and create placeholder if missing
std::shared_ptr<sf::Texture> newTexture(new sf::Texture());
if(path.empty() || !newTexture->loadFromFile(path))
{
sf::Image fallback;
fallback.create(20u, 20u, sf::Color::Magenta);
newTexture->loadFromImage(fallback);
}
else
{
std::cout << "Texture manager loaded: " << path << std::endl;
}
m_textures[path] = newTexture;
//check size of map and flush unused textures
if(m_textures.size() > 200u)
{
std::cout << "Texture Manager begin flushing textures..." << std::endl;
for(auto i = m_textures.begin(); i != m_textures.end(); ++i)
{
if(i->second.unique())
{
std::cout << "Flushing: " << i->first << std::endl;
m_textures.erase(i);
}
i--; //move back to previous position else next index is skipped
}
}
return m_textures[path];
}
std::map<std::string, std::shared_ptr<sf::Texture> > Game::TextureManager::m_textures;
Basically any textures loaded are referenced in a std::map along with the resource's file path, so if a requested texture is already loaded a reference is returned to that, rather than loading the resource again. If the map reaches a certain size when loading a new texture any currently unused textures are flushed from the cache. The class members are static, which makes it simple to access the texture manager from anywhere in my program as well as guaranteeing all loaded textures are referenced by the same map. This works very well.
However: I have been considering using this method to manage other resources, such as fonts, images or sounds. In the interest of code reuse it makes sense to me to make a templated version of this class rather than implement it for every type of resource I want to manage. This is where it falls down, because (as far as I know) I can't use static members with templates in the same way, as there will be no way for the class to know which data type / resource it's working with – and therefore I will need to start creating instances of the resource manager for each type of resource I want to manage. So this is where my question comes in: When I create these instances I want to make sure there are only ever one of each type of manager, and I need to be able to provide global access to the managers from the rest of the program, ie the resource manager would suit the singleton pattern – so would this be the exception to the rule? Or am I taking the wrong approach to resource management? (Apologies for the small essay :D )