Sign in to follow this  
c4c0d3m0n

Would this be proper usage of Singletons?

Recommended Posts

Hello @all Now, before we start, I do realise that Singletons are not popular here in the beginners forum. A lot of beginners use them improperly, using wrong justifications. I know the skillful people here like to link us beginners to articles. I've read this particular one. Still, is there absolutely no justification to use a Singleton?? I'm having trouble with my project. My project is basically a collection of classes and functions that will provide a stable base for future games/tech-demos I plan to write. One part of this project is the Log. I've decided to use a single Log, that notifies of major events (important initializations) and errors/warnings. Everything needs to access my FoobazManager There can just be one FoobazManager The two most popular reasons for Singletons, right there. However, the article suggest these don't justify Singletons. The thing is, I really hate how it seems like the Log is penetrating every function as an argument, just because those functions could do with some logging. This is especially the case with constructors. I wanted to ask if there is maybe other good articles that explain "proper" use of Singletons. Maybe some of the professional developers on this board could throw in their opinion on Singletons as well, I always value advice from the experienced :D --Rob

Share this post


Link to post
Share on other sites
Do you have many global functions?
If all your functions are in classes, then you only have to pass the log to the constructors, and store it as a member variable for member functions to use. You say you especially don't want to pass it to constructors, but in my opinion that's the best usage. You construct the object, with knowledge of the log it's supposed to use for the rest of it's lifetime.

If you have several global functions.. then either change the design and put the functions in objects, or just make a global pointer to the log and get on with your program, instead of letting it slow you down.

You could still create your log as a normal object, not a singleton, and pass it to all your objects. Just that you also set a global variable to point to the same log, and let the global functions use that. Perhaps not a very elegant solution, but it's fast, it works, and if you ever want to put those functions into objects later, it's easy to change the usage of that global pointer to a member pointer instead.

Share this post


Link to post
Share on other sites
Quote:
There can just be one FoobazManager
That sentence is incomplete. In OO, one speaks of cardinality: "There must be four wheels per car." "There must be zero or more classes per student." "There must be zero or one helmet per character." So complete that sentence: "There can just be one FoobazManager per..."

Share this post


Link to post
Share on other sites
Quote:
Original post by c4c0d3m0n
Hello @all


Now, before we start, I do realise that Singletons are not popular here in the beginners forum. A lot of beginners use them improperly, using wrong justifications. I know the skillful people here like to link us beginners to articles. I've read this particular one.

Still, is there absolutely no justification to use a Singleton??


I'm having trouble with my project. My project is basically a collection of classes and functions that will provide a stable base for future games/tech-demos I plan to write. One part of this project is the Log. I've decided to use a single Log, that notifies of major events (important initializations) and errors/warnings.

Everything needs to access my FoobazManager
There can just be one FoobazManager

The two most popular reasons for Singletons, right there. However, the article suggest these don't justify Singletons.


The thing is, I really hate how it seems like the Log is penetrating every function as an argument, just because those functions could do with some logging. This is especially the case with constructors.

I wanted to ask if there is maybe other good articles that explain "proper" use of Singletons. Maybe some of the professional developers on this board could throw in their opinion on Singletons as well, I always value advice from the experienced :D


--Rob


The article doesn't shoot down the "There can just be one instace of .." argument, it shoots down the "It doesn't make sense for there to be more than one instance of .." argument.

There is a huge difference between the two. If you cannot create more than one instance without breaking things then using a singleton makes sense , It doesn't make sense to restrict a class to a single instance just because you belive that a single instance is enough.

If you need a single instance of your log class that can be accessed from anywhere then you should create a global instance of the log class.

Share this post


Link to post
Share on other sites
Alright... I made a thinking error it seems. Singleton-ness isn't really needed. I need one Log per excecutable ;) It was a design-decision I took, to have just one Log for everything.

A global Log is the real priority though. Indeed, having more than one Log couldn't hurt, seeing it's just a glorified std::stringstream with some extra functions.

So, in theory, I could just create a global Log and use that for everything. That changes my question to:

What's the problem with globals? Why are they frowned upon? Is this a justifiable case to use a global considering I want to be "doing things the right way"?

Share this post


Link to post
Share on other sites
Quote:
Original post by c4c0d3m0n
Alright... I made a thinking error it seems. Singleton-ness isn't really needed. I need one Log per excecutable ;) It was a design-decision I took, to have just one Log for everything.

A global Log is the real priority though. Indeed, having more than one Log couldn't hurt, seeing it's just a glorified std::stringstream with some extra functions.

So, in theory, I could just create a global Log and use that for everything. That changes my question to:

What's the problem with globals? Why are they frowned upon? Is this a justifiable case to use a global considering I want to be "doing things the right way"?


The problem with globals is that it can easily get messy, you generally want your variables to have as small a scope as possible, that said even the C++ standard library uses globals, just look in iostream.h and you'll find:

namespace std {
extern istream cin;
extern ostream cout;
extern ostream cerr;
extern ostream clog;

extern wistream wcin;
extern wostream wcout;
extern wostream wcerr;
extern wostream wclog;
};



notice however that the globals are enclosed in a namespace (this makes it less messy), doing the same with your log instance is probably a good idea, (Thus its YourEngine::GlobalLog or somthing similar to access it)

Share this post


Link to post
Share on other sites
Globals are frowned upon because, in almost every instance, when someone thinks that there should be "one ____ per executable" to use terminology found in this thread, there is actually a more refined association that can be made that would eliminate problems associated with the design. For example: One texture manager per executable. Sounds nice, you throw all your textures into one big pit and that's that. Wander on down to many DirectX forums that also cater to us multi-monitor setup people, and it becomes very apparent that a "One texture manager per screen", or "one texture manager per physical adapter" type of model solves a lot more headaches. In NEARLY all cases, this sort of thing crops up, and the use of globals frequently shows a lack of foresight or a weakness of design.

There is also the issue of who gets to use the various parts. Debugging becomes easier when you know for a fact that only a small handful of your classes will be dealing with something. This means problems can only be originating from a small area when something goes wrong. For example, game logic codes doesn't need access to my texture management scheme.

Despite all the arguments against global structures, there are some spots where they are useful [which are pretty much the spots where nothing else does an outright better job]. Debug logging is really one of the last and only things I use global functions for....

Share this post


Link to post
Share on other sites
Hmm... Alright. I've decided on making the Log a global (namespaced) object. One could still create a seperate Log for a special occasion, although it would get messy in the Console possibly... I'll implement a way around that later.

I have a problem though. Even though I'm using including-guards, my linker is throwing all kinds of multiple-definition errors at me.

/**
* @author Rob Spoel
* @copyright © 2008
*
* @file R2e/log.hpp
*
**/



#ifndef _R2E_LOG_H_
#define _R2E_LOG_H_


#include <sstream>
#include <string>



namespace R2e {


class Log
{
public:

Log();
~Log();


void write( std::string );
const std::stringstream& read() const { return m_log; }


void open_sub () { depth++; }
void close_sub() { depth--; }


private:

std::stringstream m_log;

unsigned int depth;


} log; // class Log


} // namespace R2e



#endif // _R2E_LOG_H_


rob@rob-laptop:~/Development/R2e$ make test
g++ -c test/main.cpp
g++ -c R2e/engine.cpp
g++ -c R2e/gfx.cpp
g++ -c R2e/log.cpp
g++ `sdl-config --cflags --libs` -lSDL_image main.o loading.o menu.o engine.o gfx.o log.o -o application
engine.o:(.bss+0x0): multiple definition of `R2e::log'
main.o:(.bss+0x0): first defined here
gfx.o:(.bss+0x0): multiple definition of `R2e::log'
main.o:(.bss+0x0): first defined here
log.o:(.bss+0x0): multiple definition of `R2e::log'
main.o:(.bss+0x0): first defined here
collect2: ld returned 1 exit status
make: *** [test] Error 1


Is there something dodgy about the way I defined the global?

Share this post


Link to post
Share on other sites
Don't do

class Log
{
} log;

in a header. You shouldn't create an instance of Log in the header like that. Instead, do this:

// header
class Log
{
};

extern Log log;

// in .cpp source file

Log log

The problem is that every file that includes Log.h is recreating the log variable.

Share this post


Link to post
Share on other sites
Quote:
Original post by c4c0d3m0n
Hmm... Alright. I've decided on making the Log a global (namespaced) object. One could still create a seperate Log for a special occasion, although it would get messy in the Console possibly... I'll implement a way around that later.

I have a problem though. Even though I'm using including-guards, my linker is throwing all kinds of multiple-definition errors at me.

*** Source Snippet Removed ***

rob@rob-laptop:~/Development/R2e$ make test
g++ -c test/main.cpp
g++ -c R2e/engine.cpp
g++ -c R2e/gfx.cpp
g++ -c R2e/log.cpp
g++ `sdl-config --cflags --libs` -lSDL_image main.o loading.o menu.o engine.o gfx.o log.o -o application
engine.o:(.bss+0x0): multiple definition of `R2e::log'
main.o:(.bss+0x0): first defined here
gfx.o:(.bss+0x0): multiple definition of `R2e::log'
main.o:(.bss+0x0): first defined here
log.o:(.bss+0x0): multiple definition of `R2e::log'
main.o:(.bss+0x0): first defined here
collect2: ld returned 1 exit status
make: *** [test] Error 1


Is there something dodgy about the way I defined the global?
A side note: symbols that begin with a leading underscore followed by a capital letter are reserved by the implementation, so they should not be used for header guards (or anything else, for that matter).

Share this post


Link to post
Share on other sites
Quote:

The problem is that every file that includes Log.h is recreating the log variable.

And (by way of clarification) this is not what include guards prevent. Include guards only prevent the guarded file from being seen by the compiler twice for any one translation unit. Since every TU including the header gets a copy of the global, when you link them together, the linker detects the multiple definition and complains.

Share this post


Link to post
Share on other sites
Globals are not a problem in themselves. However, they make your interfaces more complex, which in turn makes it harder to understand what is going on. Procedural programming allow you to handle this additional complexity, but lacks the long-term productivity benefits of streamlined object-oriented design. On the other hand, object-oriented design has no actual tools for handling global state correctly, so it usually results in semi-procedural semi-object programs that bear the issues of both and the benefits of neither.

Read more.

In this case, however, since logging is only tangentially related to the expected functionality of your program, letting the log system be a procedural subsystem that the rest of your program connects to in a write-only fashion is an acceptable sacrifice. In that case, however, a singleton is too much work for only that.

Share this post


Link to post
Share on other sites
I don't think there ever is a proper usage of singletons.

I like analogies, so let's try this one. Objects are like tools - in this case we'll say a screwdriver. Is it wrong for someone to use a different screwdriver for each screw he fastens? It still gets the screw in, if perhaps a bit inefficiently as the worker keeps switching screwdrivers for every new screw.

If it isn't wrong, then don't prevent the consumer of your class from doing it. You're likely the consumer of your class - why would you go through extra effort to make sure that you don't create multiple instances of your class? Just like how a sane worker won't use a different screw driver for each screw he works with, you won't be creating a new log device for each message - but you might find that it's useful to have different log devices for different kinds of messages. In my analogy, this is like how the worker will find it useful to have different screwdrivers for different kinds of screws - squaredrive, phillips, flathead, torx, etc.

So what do you prevent your consumer from doing? You prevent him from shooting himself in the foot. It is wrong to attempt to drive a screw by hammering it with the screwdriver's handle. It is wrong to attempt to drive a screw with the screw the wrong way around. It is wrong to mess with wires on the interior of power tools. Ensure that consumers don't do wrong things by creating typesafe interfaces and properly privatizing variables.

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