Rethinking singleton design... why not all static?

Started by
11 comments, last by discman1028 17 years, 10 months ago
Hi, I'm starting to wonder... what are the differences (advantages/disadvantages) between: Having a class with a static accessor (GetPtr()) and static instance. and Having a class with all static members/functions? I have used the former (traditional). But what's the downsides of the latter? Perhaps I've come full circle with this question... why are singletons (as opposed to fully-static classes) necessary? (or are fully-static classes also singletons). Thanks.
--== discman1028 ==--
Advertisement
Quote:Original post by discman1028
Having a class with a static accessor (GetPtr()) and static instance.

and

Having a class with all static members/functions?


One's called a singleton, one's called a monostate. A previous thread on the subject.

Basically, with singletons, it's easier to delay or not load a singleton at all if it's un-needed. Monostates on the other hand, are easier to use without /completely/ hardcoding your code around the assumption of a single instance. On the other hand, monostates can be more confusing when used in such a manner, as changing apparently unrelated variables will effect each other.

I avoid both patterns for the most part though, like the plauge. I feel dirty whenever I use global storage. Hplus seems to talk about singletons being easier to convert from "one" to "just a couple hardcoded" in that very thread, and the chances are he's had more experience with both patterns than myself.
Quote:Original post by discman1028
Having a class with a static accessor (GetPtr()) and static instance.

and

Having a class with all static members/functions?


Well, if you have a class with all static members and all static functions, that's not a monostate, that's just a static class, and you're basically just using the class keyword for scope definition instead of the namespace keyword. You're right in that it basically is a Singleton, which means it is a global, which means it may or may not offend you depending on how you feel about globals. You've just stuck the instance in the hands of the compiler instead of your own hands. Which is why most reasonable singleton implementation contribute some kind of creation/descruction policy to the mix.

A monostate has static state in its implementation but is completely non-static in its interface (or at least non-static in the way a traditional non-singleton class would be non-static). Thus, the advantage of a monostate is that the client code simply sees a class it can instantiate and use without concern for whether or not only a single one exists in the system at a time.

Now, I personally prefer a monostate in most situations where people recommend singletons. It at least frees any interfaces that depend on it from having knowledge about whether it's a monostate or not. The only place you normally have to re-think things if you un-monostate it into a regular class is in creation/destruction code, which is more often than not pretty minimal (and easy to find).

If you use a "classic" singleton implementation with GetPtr(), etc., and want to transform it into a non-singleton class, you'll clearly have to change all access points to the class (since none of them will be referring to instances).

The problem I have with the classic singleton approach is that it makes "single instance" a property of the class interface, thus all clients but be aware of it. This may or may not be a good thing depending on the scenario. You just need to make sure you're thinking of it that way.

A monostate, on the other hand, makes "single instance" a property of the class implementation, thus clients can be completely oblivious to the fact. Again, that may or may not be appropriate to your scenario. I personally think it's more appropriate more of the time than singleton, but some disagree.

Whenever I've got the feeling that some kind of singleton solution is called for, I try to design away from it if I can. I first prefer to pass objects to other objects; if a given object isn't responsible for the construction/destruction of a specific object, it should expect to have it passed in at some point and be notified when it's no longer valid. When I find there are too many of these objects I want to provide, I simply create a container object, usually dubbed a Manager, that I pass around.

If it makes sense, I'll even provide a global point of access for a Manager, but I don't see any reason to make it a singleton; that's just using the type system as an "instance" namespace, which is not its intended purpose. Technically there's no drawbacks to doing that, but then again there are no drawbacks to simply having a global function that does the equivalent to Singleton::GetPtr()... the results are identical with identical flexibility.

So, resist singleton if you can, be aware of monostate, be aware of the decision you're making when using a singleton, and ask yourself "what am I gaining by using a singleton beyond namespacing?"
Quote:Original post by Prototype
I've used monostates many times, but it always turns out that you need to add Init() and Destroy() functions to set it up, so you find yourself emulating OO which just doesn't make sense.


I don't think I quite follow... why are you having to create Init() and Destroy() functions? The advantage of a monostate is that its an instantiatable class with a single (mono-) state shared amongst all instances. When the first instance is created (through a constructor) you simply init your state then. When the last instance is destroyed (through the destructor, probably tracked with reference counting) is destroy your state (if needed).

From the other two posts in this thread, it sounds as if people have a misunderstanding of monostates...
Quote:Original post by Simagery
Quote:Original post by Prototype
I've used monostates many times, but it always turns out that you need to add Init() and Destroy() functions to set it up, so you find yourself emulating OO which just doesn't make sense.


I don't think I quite follow... why are you having to create Init() and Destroy() functions? The advantage of a monostate is that its an instantiatable class with a single (mono-) state shared amongst all instances. When the first instance is created (through a constructor) you simply init your state then. When the last instance is destroyed (through the destructor, probably tracked with reference counting) is destroy your state (if needed).

From the other two posts in this thread, it sounds as if people have a misunderstanding of monostates...


Having global variables locked up in a class that is a singleton can have it's benefits. Firstly if you go global. the ide gives you all the possible globals and secondly you can use a function that saves/loads classes directly and thirdly you can duplicate states.
Ofcourse monostate means all states are the same, in which case the last advantage is pointless.
----------------------------

http://djoubert.co.uk
The real question, is why does it have to have global scope in the first place?

Singletons, monostates and ordinary globals are all usually bad ideas that can and should be avoided. (Note, I said usually, not always)
Quote:Original post by Simagery
I don't think I quite follow... why are you having to create Init() and Destroy() functions? The advantage of a monostate is that its an instantiatable class with a single (mono-) state shared amongst all instances. When the first instance is created (through a constructor) you simply init your state then. When the last instance is destroyed (through the destructor, probably tracked with reference counting) is destroy your state (if needed).

From the other two posts in this thread, it sounds as if people have a misunderstanding of monostates...

I always thought the point of monostate was that you don't create any objects at all, and hence don't provide any constructors. But correct me if I'm wrong, I'm never good at formal matters.

EDIT: it seems you are right, I do need to read up on Design Patterns.
Excuse my error. (post removed)

[Edited by - Prototype on June 8, 2006 5:11:46 PM]
Quote:Original post by Simagery

Whenever I've got the feeling that some kind of singleton solution is called for, I try to design away from it if I can. I first prefer to pass objects to other objects; if a given object isn't responsible for the construction/destruction of a specific object, it should expect to have it passed in at some point and be notified when it's no longer valid. When I find there are too many of these objects I want to provide, I simply create a container object, usually dubbed a Manager, that I pass around.


I created a Manager too, to solve the too-much-passing problem. I happened to make it Singleton. But why would you pass around a manager?

Quote:Original post by Simagery
If it makes sense, I'll even provide a global point of access for a Manager, but I don't see any reason to make it a singleton; that's just using the type system as an "instance" namespace, which is not its intended purpose. Technically there's no drawbacks to doing that, but then again there are no drawbacks to simply having a global function that does the equivalent to Singleton::GetPtr()... the results are identical with identical flexibility.


From this thread, I think I like the idea of Singletons better (than monostates) because if you never use them, you never waste any space on the heap or the stack. That said, snigletons are probably slower and cause slight but negligible fragmentation (from heap storage).

Quote:Original post by Spoonbender
The real question, is why does it have to have global scope in the first place?

Singletons, monostates and ordinary globals are all usually bad ideas that can and should be avoided. (Note, I said usually, not always)


Well, I always run into the problem of passing many things down a chain of classes to where I need them. It seems that there are just some things that I need all over the place ( D3D device :) ).
--== discman1028 ==--
Quote:Original post by discman1028
I created a Manager too, to solve the too-much-passing problem. I happened to make it Singleton. But why would you pass around a manager?


To break the dependancy between the code which has the manager passed to it and the manager's allocation method (singleton). This way, only the caller/owner needs to be modified, rather than both the caller/owner and the callee, if you suddenly want the option to pick between multiple managers for whatever reason. An IPv4 listener and IPv6 listener at the same time, say.

The strong point for direct use of singletons (via GetInstance()) is where the caller/owner needn't have any knowledge, ever, of the manager to be used - in other words, most likely, you know you won't need more than 1 or some small specially hardcoded value of N (like, say, 2). The "specially hardcoded N" bit is easier if you have a layer of seperation from the allocation and use, so your handshake implementation dosn't need a rewrite just to work with IPv4 and IPv6 for example.

I hate making such assumptions though. Plus, by avoiding global storage one is required by necessity to make all the links between components, helping reveal relationships between systems, as well as encouraging placing them closer together, instead of seperated by any extra layers of roundabout cruft you might tack on by accident.
Quote:Original post by MaulingMonkey
...by avoiding global storage one is required by necessity to make all the links between components, helping reveal relationships between systems, as well as encouraging placing them closer together, instead of seperated by any extra layers of roundabout cruft you might tack on by accident.


That's the best reason I've ever heard for avoiding global storage (and specifically singletons). That perfectly sums up the feeling I was trying to capture in my earlier post. I'm going to use that one!

This topic is closed to new replies.

Advertisement