Sign in to follow this  
Squirm

singletons vs monostates

Recommended Posts

Squirm    481
We had one of those never ending "singletons are evil" discussions in #gamedev last night, in which I argued in favour of the use of a singleton for the logger class which handles debug output, warnings, errors, etc. I want one, I only want one, Having two would be detrimental to its purpose, I want it everywhere, and I want it everywhere for a good reason, not because of a poor design. Washu, of course, pounced upon the term "singleton" like a rabid mongoose, and then asked why I didn't use a monostate. 15 minutes googling later, I feel I have a good grasp of what people think a monostate is. I also have no idea why it is in anyway preferable to a singleton in this case. I'm not trying to start a flamewar, I'm just asking for an explanation of why I would want to use a monostate in this case. One page went so far as to claim order of destruction was a problem with singletons, and then went on to completely fail to explain how a monostate improved on that, so my best guess so far is some sort of reference counting? [edit: please don't bother to reply with an "I love singletons" post - that's not the question I'm asking ;)]

Share this post


Link to post
Share on other sites
JohnBSmall    881
Beyond a certain point, spending time trying to pick the perfect design gives negative benefit - ie, you spend more time trying to make the "right" decision than you gain by having made that decision.
This is one of those occasions.

Think about it: You are talking about a logging system. Unless your application is called Logger Pro 2006, Enterprise Edition, you should not be wasting time on this decision. Trying to decide between singletons, monostates, just having a global instance of a log class or just writing to plain old std::cout is not a useful way to spend your time - the differences between them, in terms of functionality, is minimal (with the possible exception of just writing to std::cout).

Pick one and then get on with something interesting.

John B

Share this post


Link to post
Share on other sites
ApochPiQ    23059
The question is not useful, because it arises from a bad mentality.

That mentality is that all operations should be done by objects, which is what most people mean by the term "object oriented." That mentality is unhealthy and leads down the Dark Path to languages like Java.

You should use object-aware programming instead of object oriented programming.

Share this post


Link to post
Share on other sites
leiavoia    960
Quote:
Original post by JohnBSmall
Think about it: You are talking about a logging system. Unless your application is called Logger Pro 2006, Enterprise Edition, you should not be wasting time on this decision.


It's an issue if it comes up again in the future, so it's not necessarily a waste of time. It's like saying it's not worth my time to find out what caused a bug that mysteriously "fixed itself". If this is a personal project, understanding and experience are probably more important than the final product.

That said, singletons are nice in that they initialize all their internals like any ohter object. Monostates are basically just a bunch of static functions. Singletons are slightly better IMHO (when you really need them) because, at least in the case of the Meyers Singleton, you can guarantee the object will be there when you first call for it, even if that is before main(). That's got me out of a few jams before. YOu can also inherit them if you do it right.

Share this post


Link to post
Share on other sites
Quote:
Original post by leiavoia
Quote:
Original post by JohnBSmall
Think about it: You are talking about a logging system. Unless your application is called Logger Pro 2006, Enterprise Edition, you should not be wasting time on this decision.


It's an issue if it comes up again in the future, so it's not necessarily a waste of time. It's like saying it's not worth my time to find out what caused a bug that mysteriously "fixed itself". If this is a personal project, understanding and experience are probably more important than the final product.

That said, singletons are nice in that they initialize all their internals like any ohter object. Monostates are basically just a bunch of static functions. Singletons are slightly better IMHO (when you really need them) because, at least in the case of the Meyers Singleton, you can guarantee the object will be there when you first call for it, even if that is before main(). That's got me out of a few jams before. YOu can also inherit them if you do it right.


Quote:
Original post by Squirm
One page went so far as to claim order of destruction was a problem with singletons, and then went on to completely fail to explain how a monostate improved on that, so my best guess so far is some sort of reference counting?

Both can handle order of construction and destruction properly in C++, so that's not really an issue. With a singleton, the easiest way to accomplish this is by having your access point return a reference-counted smart-pointer to the singleton as opposed to a raw pointer, and with a monostate you just have the reference counting performed in the constructor and destructor of the type itself. That said, I will say that monostates are more natural to many.


[edit: please don't bother to reply with an "I love singletons" post - that's not the question I'm asking ;)][/quote]



Share this post


Link to post
Share on other sites
JohnBSmall    881
Quote:
Original post by leiavoia
Quote:
Original post by JohnBSmall
Think about it: You are talking about a logging system. Unless your application is called Logger Pro 2006, Enterprise Edition, you should not be wasting time on this decision.

It's an issue if it comes up again in the future, so it's not necessarily a waste of time. It's like saying it's not worth my time to find out what caused a bug that mysteriously "fixed itself". If this is a personal project, understanding and experience are probably more important than the final product.

The two situations (deciding whether to use a singleton or a monostate, versus finding and fixing a bug) are not analogous.
If a bug mysteriously "fixes itself", there is a fairly high probability that the bug is still present in your program, and is simply being masked by the change you made. There is also a very high cost if the bug is still present in the code. Therefore, searching for the bug so that you can fix it properly is a worthwhile activity - the risk if you just assume it's been fixed is greater than the cost of the time you will spend to find it.

The risk associated with choosing a monostate where you later find you want a singleton (or vice versa) is much, much less, and therefore not worth spending as much time on.
If you later find you really have to reverse your decision, then you can spend time fixing it. Refactoring code is not such a big problem that it outweighs the benefits of being able to get on with the rest of your program.

Also, if this a personal project, you'll learn a lot more by just going ahead and implementing one system, and then taking note of what parts of that system work well for your situation, and what parts work badly than you would ever learn by scratching your head for five hours to try to decide whether a monostate is a better choice than a singleton.

John B

Share this post


Link to post
Share on other sites
blaze02    100
I never really understood why there is such an argument in the first place. I've never ran into a situation where... "I need to use x's logger class. I'll just Logger * logger = new Logger(...) and hope everything works out." That is not how C/C++ work. You can't just hope everything works out. You have to understand what is going on. This is chess, not checkers. If you are going to use x's logger class, learn how to use it first or ask x for help. To prevent someone from destroying the integrity of the logger class, set up asserts. Assert that the file opened correctly. I assert things left and right, and when something breaks, I can easily find out why.

I never liked the idea of singletons (and monostates now that I know what they are, globals). Not that I'm against globals, its just that you have to remember that everything in C++ is converted to C, then assembly before it is run. When you call class->method(param), your compiler calls method(class, param). Its not magic. If you want to get really technical, method knows the first parameter is passed via the ECX register (I suppose that is a little magical if have never used assembly).

So, I agree with JohnBSmall. If you want to make a singleton/monostate, you can. You can do all the checks you want to make sure people aren't using your class incorrectly. But in the end, it won't really matter. Just make the call, code it up and be done with it.

Share this post


Link to post
Share on other sites
Quote:
Original post by leiavoia
That said, singletons are nice in that they initialize all their internals like any ohter object. Monostates are basically just a bunch of static functions. Singletons are slightly better IMHO.

Not necessarily. Monostate just have all instances of the type share the same global state. This could mean that you make all member functions static, but in my opinion, that defeats one of the main purposes of using a singleton or monostate -- to more easily make the shared state an implementation detail, as it generally should be.

Quote:
Original post by Squirm
One page went so far as to claim order of destruction was a problem with singletons, and then went on to completely fail to explain how a monostate improved on that, so my best guess so far is some sort of reference counting?

Both can handle order of construction and destruction properly in C++, so that's not really an issue (as a side note, using them for the sole reason being to solve this problem is a misuse). With a singleton, the easiest way to accomplish correct construction and destruction order is to perform reference counting using a separate counter per translation unit handled via the constructor and destructor of types in an unnamed namespace (though this will not cover all cases, it will cover most), or by having your access point return a reference-counted smart-pointer to the singleton as opposed to a raw pointer. With a monostate, you just have the reference counting performed in the constructor and destructor of the type itself. With that, I will say that monostates are generally simpler for users to work with as you can pass around instances of the type, allocate them with new and delete easily, and do just about anything with them that you can with regular types, without having a logical extra level of indirection that you would with a singleton.

Logically, both a monostate and singleton represent very similar concepts, and many people actually do refer to them as the same design pattern as they both provide a way of working with a single, global state. The main logical difference between the two is that a singleton represents an access point to a single instance of an object, whereas a monostate allows you to directly represent multiple objects which all just share the same state. I would go so far as to say that the latter is almost always preferably to the former as it is easier to make working with the object more consistent with the rest of the language, and therefore also more easily useable in generic code.

Rather than repeating my same posts from previous threads (this topic comes up a lot on these forums), you can read my replies from a similar thread from last year. I talk more about monostates and compare them with singletons starting in the second reply:

First reply

Second reply

Third reply

Share this post


Link to post
Share on other sites
voguemaster    183
Quote:
Original post by ApochPiQ
That mentality is that all operations should be done by objects, which is what most people mean by the term "object oriented." That mentality is unhealthy and leads down the Dark Path to languages like Java.


There is nothing wrong with Java. In many ways, Java is much better for development and debugging.

If you're talking about raw performance, as always that depends on the task at hand. Generalizing by saying that Java is slower than C++ is only technically true, not in reality.

When saying that Java takes away control from the programmer, that too is true but for 80% of the programmers, that's probably a good thing.

No dark path and no nothing. It all depends on your requirements.

Share this post


Link to post
Share on other sites
Oluseyi    2103
Quote:
Original post by blaze02
Not that I'm against globals, its just that you have to remember that everything in C++ is converted to C, then assembly before it is run.

No.

Quote:
When you call class->method(param), your compiler calls method(class, param).

No.

Quote:
If you want to get really technical, method knows the first parameter is passed via the ECX register (I suppose that is a little magical if have never used assembly).

Implementation defined, so No.

I recommend you invest in The Design and Evolution of C++. Or simply stop spouting inaccuracies based on pre-Standard information.

Share this post


Link to post
Share on other sites
taby    1265
Quote:
Original post by blaze02
When you call class->method(param), your compiler calls method(class, param). Its not magic.


In actuality, this is true. It's how the address of "this" is known at run-time. I would be surprised if this is non-standard behaviour. I've compiled related code in MS Visual C++ .NET 2003. I'm trying to find it for you. :)

Anyway, I don't see the harm in singletons. They are still proper objects.

In terms of your deconstructor, the exclusive use of STL's std::vector container will ensure that you never have to call std::delete[] again. Singleton class or not.

The [] (index) and & (address-of) operators work on the vector, providing both array and pointer type access directly to your contiguously stored data in RAM.

A proper C++ linker .obj(ect) is what they are not, but that's completely unrelated.

[Edited by - taby on April 24, 2006 12:42:16 PM]

Share this post


Link to post
Share on other sites
Promit    13246
Quote:
Original post by Squirm
We had one of those never ending "singletons are evil" discussions in #gamedev last night, in which I argued in favour of the use of a singleton for the logger class which handles debug output, warnings, errors, etc. I want one, I only want one, Having two would be detrimental to its purpose, I want it everywhere, and I want it everywhere for a good reason, not because of a poor design.
Your design is still poor. Only-one assumptions, implicit or explicit, are major dangers for writing robust code.

The bad design here is your logger. In general we want three things from logging: multiple logging sources (renderer, file manager, AI/logic), multiple logging sinks (file, console), and debug levels for how critical messages are. Classes will interact with a logger that has been assigned to them. Each logger object owns multiple sinks which may be configured differently, and multiple loggers can share sinks. This allows us to setup systems like so:
* The in game console gets renderer warnings and errors, but only errors from the internal managers. It does not get any messages from the AI/logic system.
* The debug file gets info messages, warnings, and errors from all sources.
This is a three logger system. One logger has been assigned to the renderer, which has two sinks attached: the console at warning level and the debug file at info level. One has been assigned to all of the internal managers with two sinks: the console at error level and the debug file at info level. One has been assigned to the AI/logic system with one sink: the debug file at info level. Note that none of the classes using the loggers know about the existence of either the console or the debug file.

You'll find that this level of flexibility is quite difficult to provide if all classes are sending their output to some kind of global object.

Share this post


Link to post
Share on other sites
MaulingMonkey    1730
Quote:
Original post by taby
Quote:
Original post by blaze02
When you call class->method(param), your compiler calls method(class, param). Its not magic.


In actuality, this is true.


No. That's roughly the C equivilant, if we were converting to C. Most (all used?) compilers convert directly to assembly these days, although what it converts to is also implementation defined. Your posted version also ignores virtual method calls, this-pointer offsets (required in multiple inheritence), etc, which will most likely be dealt with at the call site (in the aforementioned assembly).

Rough equivilant does not equate to being exactly what your compiler does, which is what the original statement wrongly implies. It could further imply that you could put method(class, param) in your C or C++ code and expect it to work. It won't.

"Singletons are evil". The main advantage of monostates (to me) is that the tokens interspersed throughout your code are saner:
m_log << "I like pie";

Instead of:
Logger::Instance() << "I like pie";

Note that, in the first case, it looks like just any old member variable. That means your code dosn't break if you change your class's member from:
monostate_logger_class m_log;

To:
std::ostream & m_log;

Or:
another_monostate_which_logs_to_some_other_destination m_log;

And add an appropriate initialization to your class's constructor. The same can't be said for Logger::Instance().

The advantage, then, is that monostates let you ignore the fact that there's only one instance of the class/object in more places than a singleton does, meaning less broken code if that assumption ever becomes faulty.

Share this post


Link to post
Share on other sites
ApochPiQ    23059
Quote:
Original post by voguemaster
Quote:
Original post by ApochPiQ
That mentality is that all operations should be done by objects, which is what most people mean by the term "object oriented." That mentality is unhealthy and leads down the Dark Path to languages like Java.


There is nothing wrong with Java. In many ways, Java is much better for development and debugging.

If you're talking about raw performance, as always that depends on the task at hand. Generalizing by saying that Java is slower than C++ is only technically true, not in reality.

When saying that Java takes away control from the programmer, that too is true but for 80% of the programmers, that's probably a good thing.

No dark path and no nothing. It all depends on your requirements.



That quote cannot be taken out of context without losing all of its significance.

Java's a great platform. The Java language is marginal. Java's obsession with objects is disgusting and causes code to be significantly more verbose than is really needed.

Writing "object oriented" code in any language tends to create designs that resemble Java programs, where every single piece of logic is contained in an "object" (i.e. class), even when that makes absolutely no sense. It is almost always possible to take an "OO" design, strip out objects where they are not strictly needed and write functional-style (or even procedural-style) code instead, and end up with a net improvement - code will tend to be more concise, more efficient, and better abstracted.

The typical "good OO design" is to build parallel vertical columns of abstraction, with small sets of objects working with each other and being "related" to a narrow hierarchy via inheritance. This model is fine in vacuum, but as soon as these objects have to start interacting, the typical solution is to build a lot of "helper objects" that take care of the interaction.

A far better way to build abstractions is in horizontal stratified layers. You basically build a mini-language; all logic should be expressed in high-level terms. Large projects may have several layers of such dialects. This produces code that is more reusable, more concise, easier to understand abstractly, and far more tolerant of small changes to specifications and requirements. (Watch the SICP video lectures for a good treatment of this.)


Using objects to model everything in a system leads to code that is needlessly verbose, buggy, prone to abstraction leakage, and intolerant of requirements changes. A classic symptom is the supposed "need" for things like Singletons and Monostates in the first place. The "canonical" Singleton, a logger, makes vanishingly little sense as an object the way most loggers are implemented. A namespace of functions would be far more appropriate.

Even Monostates are, in general, a poor attempt to disguise sets of functions as objects.


Some things just are not objects, and shouldn't be modelled as such. That's the point of my journal post, and the meaning of my reference to Java earlier.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
Quote:
Original post by ApochPiQ
Some things just are not objects, and shouldn't be modelled as such. That's the point of my journal post, and the meaning of my reference to Java earlier.


Ok, just a question, do you have any real world experience with large projects?

I'm asking because it's fashionable for "academic boys" to bash Java showing how better is, and in many cases people really lack any actual experience.

The "Large projects may have several layers of such dialects" part is quite amusing. I imagine what maintenance of a system will be after some years when some highly creative individuals, exercising all "the freedom" such a language entitles them to, created several "dialects" for working on it.

Or even better, how resource management inside of a company will be like if whenever a developer is moved from a project to another he will need to learn new "dialect(s)" created by highly creative individuals.

I'm not saying that you are 100% wrong, but I would like to see this in real world.

Share this post


Link to post
Share on other sites
JBourrie    1204

Quote:

Some things just are not objects, and shouldn't be modelled as such. That's the point of my journal post, and the meaning of my reference to Java earlier.


I read the journal post, and while I agree with much of it in theory (to force everything into an object, even the application itself like Java and C# do, is ugly and inflexible), I think you may be taking it a bit far when it comes to denouncing object orientation.

Objects are a tool, but most of the time it is the best tool for the job. Anything that needs access to it's own data is immediately a conceptual object. Either you can create this object as a global (usually a global object, and only for the big stuff that spans the life of the application) or you can create it as a local object to some scope.

Of course, it is overkill to implement a collision library as a hierarchy of "Object->Sphere/Cube/Teapot->SquashedSphere..." because this creates needless complexity.

But a logging class could be a great object, consider it as a notebook... that's an object isn't it? It takes a log, stores all of the debug output, warnings, and such, and then can spit it out to a file or analyze it for anything you need to know. Sure, this could be done using a big global array of something or other, but it's better as an object.

Your logic is sound in theory, but it sounds like the only thing you think should be objects is something that you could hold in your hand. In reality, anything that is better represented as an object then not as an object (pretty much anything that needs it's own data) SHOULD be an object.

Object Aware. I like that. You just also need to be aware that they're damn useful most of the time. Just don't make your main() loop an object.

Share this post


Link to post
Share on other sites
MaulingMonkey    1730
Quote:
Original post by Anonymous Poster
Quote:
Original post by ApochPiQ
Some things just are not objects, and shouldn't be modelled as such. That's the point of my journal post, and the meaning of my reference to Java earlier.


Ok, just a question, do you have any real world experience with large projects?


Judging that his Homepage lists his employment at Egosoft... I would assume so. Note that he does not encourage multiple "dialects" doing the exact same thing as you would seem to imply later in your post, rather, he encourages a single mini language per layer.

Examples: Direct3D, OpenGL. Each are effectively mini languages for rendering graphics, utilizing words like "Texture", "Vertex", and "Color". Most people had to learn these, just like they would need to learn the functions of any other code base.

This is known as "creating functions and giving them appropriate names". When applied to a single group of common logic level: "creating related functions and giving them appropriate names". Common sense, really.

"There is nothing wrong with Java" made me laugh. There's something wrong with every language, which is why there's the whole concept of tradeoffs, and why people havn't chosen a single language to do everything in :-).

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this