singletons vs monostates

Started by
15 comments, last by MaulingMonkey 18 years ago
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]
Advertisement
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.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
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.
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.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

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.

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.

Check out my new game Smash and Dash at:

http://www.smashanddashgame.com/

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 :-).

This topic is closed to new replies.

Advertisement