Singletons and Memory Management

Started by
40 comments, last by thre3dee 16 years, 10 months ago
Hi all - Recently there has been a lot of discussion about Singletons on the boards, and I've noticed a lot of people are staunchly against them. I think I understand a lot of the arguments, and I'm starting to agree that in general, Singletons are a Bad Idea(TM). However, I'm still not fully convinced that they're _always_ bad, which means I might still be missing something. The two conditions for Singletons being appropriate are - 1) Only one instance is allowed, having multiple instances would be incorrect 2) Global access is required Much of the conversation has been about various major engine components not being appropriate as Singletons. (Logging keeps being brought up as a possible exception) I've been working on a memory manager, and I'm starting to feel like it should definitely be a Singleton, since I'd like to route all allocations through it. Specifically, I feel like the final game system should have a Singleton Primary Memory manager, which would keep track of the heap. The primary logic I'm seeing is that if the whole system is supposed to run out of a single memory pool (like on a console), then it would almost certainly want global access (so everything can use dynamic allocation) and you definitely would want only one (there's only one hardware resource). I know that you could simply "choose" to only instantiate one PrimaryMemory manager, but it feels like it would be wise to have the code enforce it. Is there a problem with this logic? Is there a more appropriate way to handle this than as a Singleton? Anyhow, it is certainly possible that I'm missing some aspect of the discussion against Singletons, and if that is the case, I'd like to clear it up. Thanks for any help!
...
Advertisement
Typical questions to decide whether something should be a singleton:
  • Is there a reason why that something cannot be implemented to have more than one instance? The important element here is about implementation impossibility, and not about how you feel the class should be used. In short, can you implement your manager so that two (or more) can coexist, and if not, why?
  • Is there a reason why that something should be an object? Are you passing the instance by argument, or keeping references to it, anywhere? In short, why can't you overload operator new and operator delete instead?


Depending on your answers to these questions, your manager should be a new/delete overload, a singleton inheriting from an allocator base class, or a non-singleton class inheriting from the same.
Quote:Original post by Stoic
I've been working on a memory manager, and I'm starting to feel like it should definitely be a Singleton, since I'd like to route all allocations through it.

How can you be so sure that you're never going to want to use two memory managers, for different purposes?

If you want a globally accessible memory manager, you could just make a regular global instance. Then, if later on, you decide you want to use multiple globally accessible memory managers, you can just create another global instance.
With a singleton, you'd be screwed.

Quote:
The primary logic I'm seeing is that if the whole system is supposed to run out of a single memory pool (like on a console)

Is there any reason why a console must be treated like a single memory pool?
And is there any reason why the memory allocator handling this must be an object? (malloc isn't an object. Just a function. new/delete overloads aren't objects either)

Quote: and you definitely would want only one (there's only one hardware resource)

So?
What if I say "all addresses below X are handled by this manager, above X by that one"?
There's only one GPU too, but it's certainly possible to have multiple renderers. Or multiple threads on a single-core CPU. That's hardly a valid reason.

Quote:I know that you could simply "choose" to only instantiate one PrimaryMemory manager, but it feels like it would be wise to have the code enforce it.

What about cout? Do you ever *accidentally* create a new ostream instead of printing to cout? It's not a singleton, but just a global object. I've never heard of anyone accidentally instantiating new ostreams when they just intended to output to cout... [lol]
So does the code really need to enforce that?

What happens once you need two different memory managers? One for ints, one for dynamic-sized blocks.

What happens once you need 25 different ones. One for ints, one for ints for STL, one for ints allocated within a tight loop, and one for ints used by rendering loop?

Also - what happens once you have two threads? Will you lock every call to the memory manager?

Obviously, these entities will be static and somewhat global in nature. But singletons are never implied, since they don't exist in OO. There is no "only one instance of".

Singletons are appropriate under one circumstance only - they hold invariant state. Your memory manager cannot do that.
Quote:Original post by Stoic
1) Only one instance is allowed, having multiple instances would be incorrect

"Incorrect" is a weaselly term. Incorrect with respect to what? The way in which it can be used? The way in which you intend to use it today? A singleton should only be used if having multiple instances would be (drum roll please) impossible. If the computer simply would not support it. Loggers don't fit that mould. Neither do memory managers, unless you're programming for embedded systems without an MMU.

BTW, the "global access" thing is sort of a red herring, which tends to confuse the issue of singletons. ToohrVyk's criteria are good ones.
I think something to remember is that when people say "ThisOrThat shouldn't be a singleton", it should be taken with a grain of salt. Their idea of what ThisOrThat looks like is different from yours.

For example, lots of people use the graphics *gulp* "manager" (sorry, couldn't resist hehe) as an example of something that shouldn't be a singleton, and they make arguments like "well how will you support multiplayer?" or "well what if you want two different views into your game?"

But the bottom line is, a "View" into your game is not the same as "the thing that talks to your graphics card." If I have like a graphics driver thing that's an interface to the hardware, and initializing it, in SDL lingo, initializes the video subsystem, there should only be one. You can have several VIEWS into it, but I can't imagine why one instance of a game should have more than one thing talking to the video card. As for multiplayer, your computer won't be rendering to your friend's computer's screen, so each box will have an instance of it. That's still fine. What if you have..... TWO screens? Still one graphics card... What if you have two graphics cards and two screens? ...well is it worth designing that in, for the .1% of people who would benefit from that? I know there are plenty of cases where singletons are abused (e.g. logging, much of the time), but you STILL need to take those arguments with a grain of salt.

Designing for massive extensibility is a great thing, but when it comes down to it, you have to draw the line somewhere and say "vectors of n interfaces to the graphics card aren't supported in version 1.0" or else you'll never get anything done.

Quote:Original post by Replicon
For example, lots of people use the graphics *gulp* "manager" (sorry, couldn't resist hehe) as an example of something that shouldn't be a singleton, and they make arguments like "well how will you support multiplayer?" or "well what if you want two different views into your game?"


These are frequent arguments against global variables, and are not really adapted to singletons.

The main argument against 99% of singletons is that there is no reason for these classes to be singletons at all—making them normal classes and creating a global instance of them would achieve the same effect with less overall complexity. In some cases, they shouldn't even be objects: they should be a set of functions.

Ok, I think I see what you guys are saying. If you don't _have_ to make it a Singleton, don't do it.

Let's see if I understand this properly. In my original design, I was thinking of something like this -

(Assume that you have a non-singleton class called HeapManager, which manages a big chunk of memory for you)

Pseudocode:
class HeapManager{   //interface for managing a heap, allocating, deallocating, etc..private:   unsigned char heapData*;   unsigned long heapSize;   //Additional data for keeping track of allocations -    //  lists, maps, etc.}//Intend to use this as our system's Primary Memory Managerclass TheMemoryManager: public Singleton<TheMemoryManager>{    //wrapper to interface of SystemHeap;    //  maybe some additional Loggingprivate:    //will be the size of say, a console's RAM    HeapManager SystemHeap;};


The design above seems rather sound - if you want multiple heaps, you can simply allocate them through the Singleton(they'll still be contained within the Single original Heap). However, the argument is that it's better to have the HeapManager from TheMemoryManager be a simple global, or perhaps wrapped by overloaded new and delete, right? That way you can still allocate the additional Heap Managers, but you never created the Singleton.


Am I on track here? Sorry to keep beating a dead horse...

...
Quote:For example, lots of people use the graphics *gulp* "manager" (sorry, couldn't resist hehe) as an example of something that shouldn't be a singleton, and they make arguments like "well how will you support multiplayer?" or "well what if you want two different views into your game?"


In Java with the design pattern movement, singletons gained fame overnight. One year after, everyone was ditching them.

Why is it possible, viable and desirable to write a whole Java application without a single singleton? And I'm talking huge applications here. Millions of lines of code (most of it auto-generated, but still).

The only real difference between C++ and Java is Java's implied garbage collection.

So is the lack of garbage collection what makes singletons so apealing?

The true problem comes from "there will only be one". That is what conflicts with OO design, where you have relations between objects. Static or global isn't an issue.

It's also part of culture. Everyone does it. So why risk, and go the other way. And so, the circle is complete. New tutorials are written using singletons, a generations grows up learning from that aproach, writes their own tutorials, using singletons, ....

It's a very important topic about the overall design. Singletons are really simple, convenient - yet their flaws have been demonstrated often. And unfortunately, when relying on 3rd-party software, they are always there.

The irony is, they aren't that evil - it's their overabuse that is.
Quote:Original post by ToohrVyk
Quote:Original post by Replicon
For example, lots of people use the graphics *gulp* "manager" (sorry, couldn't resist hehe) as an example of something that shouldn't be a singleton, and they make arguments like "well how will you support multiplayer?" or "well what if you want two different views into your game?"


These are frequent arguments against global variables, and are not really adapted to singletons.

The main argument against 99% of singletons is that there is no reason for these classes to be singletons at all—making them normal classes and creating a global instance of them would achieve the same effect with less overall complexity. In some cases, they shouldn't even be objects: they should be a set of functions.

Its funny because theres all these people that believe that you have to remove all accessibility from a project *just* so that you don't have any globals (either as Get* functions or global variables (yuck) or singletons (almost yuck)). But I've been thinking and thinking about this and I seriously can't find any way that you could make a game without having SOMEthing global so that each cpp or DLL can access the shared subsystems. I just don't see the point in going to those great lengths. All it does is lower the usability of the game code. Even if the engine itself is entirely non-global (say a BIG set of free-floating, ready to go systems).

This topic is closed to new replies.

Advertisement