Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: A question about the dreaded singleton  (Read 5108 times)

0 Members and 1 Guest are viewing this topic.

fallahn

  • Sr. Member
  • ****
  • Posts: 492
  • Buns.
    • View Profile
    • Trederia
A question about the dreaded singleton
« on: August 12, 2013, 03:03:50 pm »
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 )

FRex

  • Hero Member
  • *****
  • Posts: 1845
  • Back to C++ gamedev with SFML in May 2023
    • View Profile
    • Email
Re: A question about the dreaded singleton
« Reply #1 on: August 12, 2013, 04:02:48 pm »
Woah there, std::map has find which returns interator and has log n complexity and you're doing n complex linear search. Also erase returns iterator to element after erased one so you don't have to do --i. You might also want to use make shared(because it allocs shared ptr and the class in one go, as opposed to new) and maybe some weak pointers.

Wall of text(I'm not liable for any (brain) damage that might happen as a result of reading my opinions):

I once seen singletons(which were very similar to doom 3 way of doing globals) suggested in a book but I was kind of against globals and singletons but after reading doom 3 I'm leaning towards well thought out (possibly pimpl'd) pointers to some systems. Globals and singletons seem to be really hated by many programmers, it's one of these religious wars, akin to Java vs. c++ and C vs. c++, you are the programmer and your program belongs to you, you must make the decision to go with what you think is best and then learn from your mistakes.

Like what doom 3 does with most/all of it's major system is make them all pure virtual and then make extern pointer and assign it in .cpp file. And they all are alive for entire duration of the program but have Init and Shutdown methods. And the pointer can in theory be assigned to something else if you want to override that system with your own(I don't know if that's part of the reason it's pointer and not reference).
Also cvars are global variables exposed to the dev console(basically). There is also a funny thing going on with cvars and how extern ptr in one header maps to different variables in different dlls but I won't get into that ;).
The thing is that pretty code is good but working code is even better. I've seen games coded in hsp, it has only globals. Tons of games use globals. Doom 3 does it quite well actually, it doesn't globalize render world, or render window or parsers for script, only main program script of the game is globalized. Carmack said he is not familiar with entire codebase, just that he laid out rough sketch for entire engine = that way of use of globals is very tight and doesn't leak abstractions or make things non localized, that is quite great and beats one of reasons against using globals. And it actually makes sense to use globals like that. Imagine you had settings screen that lets user choose difficulty, you'd probably stuff that variable in a config file or something similar and basically use filesystem like a global variable dump except it doesn't feel nearly as dirty. Singleton is to me just bastardized global(I know it's supposed to be something more), it usually ends up as that, 'its not global, its a singleton, im a good programmer :( ', the way doom 3 does it is actually like a singleton(the entire derived class is in .cpp, you can't instance another one) that is swapable(extern ptr) and doesn't require recompilation of every file that includes it's header to perform that swap(because it's a pure virtual interface), sounds rather good, right? Globals are so so useful when used right, but people have silly ideas(at my old college someone wanted to use global to return 2 values from function).
Doom 3 is really quite ingenious engine, it's c++, no eye bleeding templating, not overly using globals, configurable, scriptable, exstensible, open source and it's bearable for visuals even today, aged well, and just around 600k of code. Just wow, right?

I at one point thought that cvars had a problem in them: the local ones in functions that get destroyed at scope exit would not remove themselves from linked list. Then I read a bit more and found that the linked list of cvars ceased to exists after cvar system is init and takes the variables and copies them so the actual cvars are now pointers/handles to internal ones, managed by cvar system. And when is the cvar system init? BEFORE any function can be called, so BEFORE any local cvar can be created. This is perfectly thought out.

Globals often have following pitfalls listed:
1. Non locality of code.
2. Code is harder to test.
3. Initialization order is pretty much random for globals in different files(famous global sf::RenderWindow crash).
4. There is only one instance of singleton, what if you want more?
5. Concurrency.

1 is kind of invalidated as soon as you only globalize doom style the huuuge systems, like the filesystem
std::ifstream file("hey.txt"); turns into myengine::filehandle f=filesys->getFile("hey.txt"); and so on, it was pretty global-ish to begin with and you just sprinkle some additional info like list of directories to look for "hey.txt" in. For many things like opengl, openal, filesystem, you'd use global functions anyway to enforce some sort of state for them to be in, there is one graphics card, one audio output, you can have few windows or sounds but do you need few texture or audio buffer managers and have few copies of every resource? Lying side by side on same ram/gpu, wasting space? No.

2 and 3 are completely void because of the way doom 3 works. You can pretty much make a testing subsystem for every class and assign global pointer to that before initing. The global systems classes do nothing heavy on normal ctors and dtors and init and deinit order is tightly controlled by your explicit function calls so you do it in right order. That + assert(whateversystemineed->isInit()) when a major subsystem relies on another one being initalized before. In case of cyclic dependancies: don't init all at once.
5 is just eh.. that's what atomics and mutexes are for and this is not issue with just globals and singletons, things that are intrinsically thread safe are rare and far between so it's up to the programmer to make them safe and very very often the app is single threaded anyway.
4 is sort of anti-yani(you ain't gonna need it) pattern. Do you really need two filesystems? Two dev consoles? Two instances of your entire game? It could be cute and proof how global-less your code is to run two games at once in one exe but the thing is you won't ever use that 'freedom' to have more than one of whatever it is you are making global or singleton because this is not your goal.

Bottom line: do it doom 3 way, globalize only MAJOR sub systems and don't use filesystem or struct of pointers to 20 subsys classes that you pass everywhere instead of a properly implemented globals like that.
« Last Edit: August 12, 2013, 04:07:13 pm by FRex »
Back to C++ gamedev with SFML in May 2023

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10821
    • View Profile
    • development blog
    • Email
Re: A question about the dreaded singleton
« Reply #2 on: August 12, 2013, 04:18:12 pm »
DOOM 3 IS THE BEST NOTHING ELSE SHALL EXIST BESIDE IT!!!!!!!11111
Didn't we have that already once in the past?
It's one way to go and it worked for Doom 3, but Doom 3's code base is already bit older and a lot has changed in the past ~10 years, especially regarding C++.
I'd say, thanks for explaining how Doom 3 does it, but please stop generalizing stuff up to the point where Doom 3's codebase seems the most glorious and best of all time... :-\

Globals and singletons seem to be really hated by many programmers, it's one of these religious wars, akin to Java vs. c++ and C vs. c++, you are the programmer and your program belongs to you, you must make the decision to go with what you think is best and then learn from your mistakes.
Also stop spreading such generalized and simply false statements.

Thank you! :)

As for the OP, a resource manager is not an exception at all, in fact, it's most often used for that case on the forum, where we had already many of those discussions.
The main problem is: You break encapsulation, by making it possible to access the resource manager from everywhere at any time. Not every single class should be able to access it.

If you need access to the resource manager in many different classes, then you might want to reconsider your code design. Classes should have a minimal dependency to other classes.
« Last Edit: August 12, 2013, 04:26:27 pm by eXpl0it3r »
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Ixrec

  • Hero Member
  • *****
  • Posts: 1241
    • View Profile
    • Email
Re: A question about the dreaded singleton
« Reply #3 on: August 12, 2013, 04:55:41 pm »
Maybe this is slightly off topic but there's one thing I never understood about all the anti-global variables arguments.

Quote
2. Code is harder to test.

Why exactly is this?  I have a ton of globals in my program (all bunched up in one place, mind you) and I don't think they've made it any harder to test.  Does that mean I made the correct decision regarding what should and shouldn't be global, or am I missing something that's silently hurting my productivity?

fallahn

  • Sr. Member
  • ****
  • Posts: 492
  • Buns.
    • View Profile
    • Trederia
Re: A question about the dreaded singleton
« Reply #4 on: August 12, 2013, 05:03:09 pm »
Woah there, std::map has find which returns interator and has log n complexity and you're doing n complex linear search. Also erase returns iterator to element after erased one so you don't have to do --i.

Ah thanks! I guess I need to brush up on my containers some more. I've not used std::map often.

Thanks for the feedback / opinions. It's true I've left this a bit late in my design to actually be considering now, but we live and learn. I understand what you mean about the Doom 3 engine, and I think there is definitely something to be taken from it - but I'm not sure I want to create a carbon copy.

As for the OP, a resource manager is not an exception at all, in fact, it's most often used for that case on the forum, where we had already many of those discussions.
The main problem is: You break encapsulation, by making it possible to access the resource manager from everywhere at any time. Not every single class should be able to access it.

OK, so I'm right in thinking this is one of those times where a singleton would come into use, but rather than give it global scope provide controlled access to it via perhaps a reference stored as a member of a class which needs to access it? I can totally understand graphical classes not needing any access to a resource manager for sounds :)

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10821
    • View Profile
    • development blog
    • Email
Re: A question about the dreaded singleton
« Reply #5 on: August 12, 2013, 05:28:36 pm »
Quote
2. Code is harder to test.
Why exactly is this?  I have a ton of globals in my program (all bunched up in one place, mind you) and I don't think they've made it any harder to test.  Does that mean I made the correct decision regarding what should and shouldn't be global, or am I missing something that's silently hurting my productivity?
Not sure either, since I haven't heard a lot about this argument. I can imagine that one option is, that the state of the global can't be looked at in a decoupled system.
If all the class have minimal linkage between each other, you can very easily take one class and test everything with it, very easily, but once you get globals playing into it, the state of a class can't be "frozen", because the global can change at any point in time, by any other object out there. So it's basically an issue of guaranteed states.
This doesn't mean it has to be harder to test stuff, but it implies, that it can be harder. ;)

OK, so I'm right in thinking this is one of those times where a singleton would come into use, but rather than give it global scope provide controlled access to it via perhaps a reference stored as a member of a class which needs to access it? I can totally understand graphical classes not needing any access to a resource manager for sounds :)
Well if you change your design around, there's no need for singletons, because by the design itself you can ensure that there can't exist multiple versions of the resource managers. ;)
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

FRex

  • Hero Member
  • *****
  • Posts: 1845
  • Back to C++ gamedev with SFML in May 2023
    • View Profile
    • Email
Re: A question about the dreaded singleton
« Reply #6 on: August 12, 2013, 05:34:01 pm »
Quote
DOOM 3 IS THE BEST NOTHING ELSE SHALL EXIST BESIDE IT!!!!!!!11111
I didn't say that, I pointed out the ways in which it shows very clearly designed APIs and systems and correct use of globals while not doing any of the 'correct' things according to certain programmers. I don't agree with certain things that after 10 years are obsolete ie: in-house rtti and in-house scripting and data description language(a role which nowdays Lua 5.x or one of other scripting languages can fill very well). Both good and bad practices lists go on and on and perfect code doesn't exist(which many people seem to keep forgetting).

Quote
I'd say, thanks for explaining how Doom 3 does it, but please stop generalizing stuff up to the point where Doom 3's codebase seems the most glorious and best of all time... :-\
Show me a more feature rich (fps) engine, which is gpl, open source, used to be top-of-the-line commercial grade engine with best renderer(ie. unified shadow and lighting) at its time, aged well and is still used in mods and games and at the same time didn't explode to few million lines. Please do, I really wish to learn and if you seemingly have a better resource than that I'm all ears...



Why exactly is this?
It is often said by anti-globalists that if you were to have Modules A, B and C that utilize globals, they can mess each other up but when you try and test one module on its own it won't experience the same problem because it was introduced by the way in which other two altered global variables which it uses. Also you can't move the class to another project without moving globals and possibly other classes that set up the said globals.
I personally disagree. :P

Quote
Also stop spreading such generalized and simply false statements.
None of the three is generalization, you just flat out forbade global resource manager. :P
Back to C++ gamedev with SFML in May 2023

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: A question about the dreaded singleton
« Reply #7 on: August 12, 2013, 06:56:05 pm »
fallahn, you can take a look at the resource caches I implemented in Thor. From what I've understood, they seem to cover exactly your needs, so you don't have to reinvent the wheel. And of course they work without singletons or global variables :)

However, it may be well possible that you do not need the whole flexibility of shared ownership and everything. If you can ensure that resources live long enough, something simpler might be appropriate, for example the resource holders we developed for the SFML book.


IGlobals and singletons seem to be really hated by many programmers, it's one of these religious wars, akin to Java vs. c++ and C vs. c++, you are the programmer and your program belongs to you, you must make the decision to go with what you think is best and then learn from your mistakes.
It's not a religious war, there are loads of rational arguments against global variables and singletons. We have discussed them often enough in the past.

Quote from: FRex
Just wow, right?
Ehm, no. I've already explained that here. Today, good C++ code definitely looks different than Doom 3.

Quote from: FRex
[non-locality of code] is kind of invalidated as soon as you only globalize doom style the huuuge systems, like the filesystem
Not really, it's even more important for huge systems that are accessed from anywhere. By avoiding globals, you stop using the huge system in arbitrary places and you design the application such that components that need access to it are explicitly granted access. This gives a much clearer overview over dependencies across different modules.

Quote from: FRex
For many things like opengl, openal, filesystem, you'd use global functions anyway to enforce some sort of state for them to be in
Yes, and especially in OpenGL this leads to very annoying bugs. You change a state, and it affects rendering in completely unrelated places and only under very specific conditions. It is extremely easy to mess something up. There is a reason why SFML abstracts everything away and provides an OOP API. But even for seemingly simple tasks, there is a lot of work necessary because of the way OpenGL works -- just have a look at the sf::Shader::setParameter() overloads.

Quote from: FRex
The global systems classes do nothing heavy on normal ctors and dtors and init and deinit order is tightly controlled by your explicit function calls so you do it in right order.
You are aware that you're just advertising manual construction and destruction over established principles like RAII?

Quote from: FRex
that's what atomics and mutexes are for
No, these are for synchronization. Synchronization is not always needed, and if so, it introduces additional complexity and performance overhead. Depending on the problem, instead of accessing always a global object, it may be possible to cache the object locally and synchronize it only when needed.

Quote from: FRex
the thing is you won't ever use that 'freedom' to have more than one of whatever it is you are making global or singleton because this is not your goal.
And because one does usually not exploit the freedom, a single instance has to be enforced, approving all the disadvantages of singletons?
« Last Edit: August 12, 2013, 06:59:36 pm by Nexus »
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

fallahn

  • Sr. Member
  • ****
  • Posts: 492
  • Buns.
    • View Profile
    • Trederia
Re: A question about the dreaded singleton
« Reply #8 on: August 12, 2013, 07:06:28 pm »
Heh, I'm almost beginning to think I shouldn't have brought up this subject :)

Well if you change your design around, there's no need for singletons, because by the design itself you can ensure that there can't exist multiple versions of the resource managers. ;)

Yes of course, I'm confusing myself now that just because there is only one instance doesn't make it a singleton (it's been a long day...)

@Nexus: those are perfect examples, thanks. I shall be studying them carefully in due course. This is what I love about the whole world of programming (and sometimes hate ;) ) there's always more to learn.

Thanks for all the input guys!

FRex

  • Hero Member
  • *****
  • Posts: 1845
  • Back to C++ gamedev with SFML in May 2023
    • View Profile
    • Email
Re: A question about the dreaded singleton
« Reply #9 on: August 12, 2013, 08:07:48 pm »
Quote
Heh, I'm almost beginning to think I shouldn't have brought up this subject :)
You probably shouldn't ;D

<post cut out>
Actually, whatever, I'm not going to convince anyone anyway, it's offtopic. :P

Edit: But anyone is free to send me PMs of any kind about this.
« Last Edit: August 12, 2013, 08:22:10 pm by FRex »
Back to C++ gamedev with SFML in May 2023

MorleyDev

  • Full Member
  • ***
  • Posts: 219
  • "It is not enough for code to work."
    • View Profile
    • http://www.morleydev.co.uk/
Re: A question about the dreaded singleton
« Reply #10 on: August 12, 2013, 10:40:07 pm »
The difficulty in testing comes from how in order to test you have to initialise all the dependencies of a class/function, either to fakes, mocks or real implementations.

And since when using the Singleton design pattern some of those dependencies are global, that means that class/function hasn't declared it needs to go off to that singleton explicitly, so knowing to do that requires either overly specific documentation or going through the source code under test line by line. Neither are desirable and mean the code is not inherently self-documenting.

This is related to a general problem with the Singleton design pattern: Any usage of them is implicit. The usage of the global singleton is a non-obvious side-effect, and that's the kind of place that at best just wastes some of the programmers time and at worst is where the crashes, bugs, glitches and mistakes love to hide.

A few good blog posts on this can be found here:
http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/
http://misko.hevery.com/2008/08/21/where-have-all-the-singletons-gone/
http://misko.hevery.com/2008/08/25/root-cause-of-singletons/

There is little wrong with your code having only one instance of a class (singleton), that's an often unavoidable state, but if you were to go and take that instance and shove it into a global (Singleton design pattern) is a bad idea for all the reasons global variables are bad ideas.

It's almost always better to have one instance of something you explicitly pass around to what needs it, and have the lifespan of that instance be only as long as it's needed.
« Last Edit: August 13, 2013, 01:21:32 pm by MorleyDev »
UnitTest11 - A unit testing library in C++ written to take advantage of C++11.

All code is guilty until proven innocent, unworthy until tested, and pointless without singular and well-defined purpose.