9

I'm pretty new to C++ and am writing a cross-platform (desktop/mobile) 2D game engine... my question is, am I using singletons in an appropriate manner and if not, is there an alternative?

Basically I have a few components to my engine which are built around a singleton object. Examples:

VBOManager (Singleton)
This "manager" is basically responsible for allocating and of course, "managing" vbo's that are used to store texture mapping and vertex coordinates. I control "read/write" through this object so I can cache data written to the vbo by other objects and return pointers (avoiding storing duplicate data like 500 sprites with the same mapping and vertex coordinates).

TextureManager (Singleton, self explanatory)

GLUtils (Singleton)
I pretty much use this for unifying common GL calls which are different based on the current platform, like GL or GLES. Example, GLU functions (libglu isn't on android so I have a custom implementation)

Graphics (Singleton)
Used to handle things like window initialization and resizing, global framerate management, viewport initialization etc.

InputManager (Singleton, self explanatory)

Anyway so here I am reviewing all of this and I'm starting to feel really, really dirty. I do feel that given the requirements and functionality of the given objects, it's justified. But on the other hand I'm a newbie and something just doesn't feel right about having this "pattern" so rampant throughout my code. If the latter is indeed the case, what might be an alternative?

  • 9
    The sole fact that you're using them makes them overkill. :) Search SO for some good argumentations against singletons, or alternatively visit the `Lounge` chat and bring the topic up if you're feeling brave. – Xeo Nov 18 '11 at 03:37
  • 2
    Here's one: http://stackoverflow.com/questions/3319434/singleton-pattern – Fred Larson Nov 18 '11 at 03:41
  • 2
    +1 for asking before doing it :) – John Humphreys Nov 18 '11 at 03:46
  • 2
    Here's another: http://stackoverflow.com/q/1392315/10077 – Fred Larson Nov 18 '11 at 03:48
  • You're new to C++ and are trying to make a cross platform game engine... kudos for being ambitious. I suggest starting by learning a lot more about programming in general including design patterns before attempting something as complex as a game engine (especially a cross platform one!). – NickLH Nov 18 '11 at 05:03
  • @Ascension Systems - If you are not already doing unit testing, get into it. If you have objects to test that rely on Singletons, you will soon find it makes them harder than necessary to test. Singletons add dependencies. – Lieven Keersmaekers Nov 18 '11 at 08:19

7 Answers7

7

Before I begin, I want to make to clear what we're talking about so that we're all on the same page. A Singleton is:

  1. "an object," This means that it has certain internal state, which is encapsulated from direct external access. It has a lifetime: it is created at some point in the program's execution, and it is destroyed at some later point.
  2. "of which no more than one instance can be created," This means that there is some explicit machinery in place that forcibly prevents creating multiple instances. The constructor and destructor will be called no more than once in the program's execution.
  3. "which is globally accessible." The instance, once created, is accessed either via a global function call or a public static member of a class.

A global object is not a singleton; it is simply a global object. If it is possible to make more than one, it is not a singleton.

Now that the definitions are out of the way:

VBOManager (Singleton)

I cannot imagine how this object works in any way that makes sense. Buffer objects are, in terms of OpenGL, free-standing constructs. They have no relation to one another. So I don't understand why you would need a manager that handles all of them.

I can understand having a specialized wrapper buffer object class for dealing with streaming buffer objects. There are several ways to do streaming, and some are more performing than others. So it makes sense to wrap that in an object.

But to have a global buffer object manager does not make sense. Buffer objects should be independent of one another. Or if anything, they should be dependent on other objects like Meshes (multiple mesh objects can reference the same buffer(s)). But you shouldn't need to have a global buffer object manager.

I can understand having a repository of named objects from which you can add and retrieve. But I don't understand the need to have it for specific object types like this. And the need for it to be a global singleton is... dubious.

I find that singletons are best for concepts that are fundamentally unique. There is one filesystem, and therefore it makes sense to have a global filesystem. OpenGL itself is global (owing to its C-based nature), though even then, it is possible to switch rendering contexts. If it ever makes sense to have two of something, then a singleton probably isn't the best way to go.

Even if you are only using a single buffer object, and have a manager for it, there is no need to make this class a singleton. You can make it a global if you like, but there is no need to forcibly prevent users from creating more than one instance of this class. If later, you want to have multiple buffer object managers (and possibly pay the performance price), so be it.

GLUtils (Singleton)

Why is this an object? The Singleton pattern refers to an object that there can only ever be a single instance of at any one time. Utility functions are simply global functions.

C++ is not Java. You don't have to put everything in a class. Global functions are not bad design. Indeed, they are good design, where appropriate. If a function doesn't need to access any state (outside of its parameters and any other functions it might call), then there is no need for it to be part of an object.

Graphics (Singleton)

This is something for which one can argue that a Singleton makes sense. It is a living object with state. And you explicitly do not want more than one in your application.

However, consider this. Is it so wrong to have more than one? How often would you be passing around this Graphics object? How much code needs to work at the level of the Graphics object? I'm guessing not a lot. Some basic initialization code, and that's it.

So while it is defensible, it isn't necessary. It's not a concept that requires being a Singleton. Plus, by being able to have more than one, you can delete it at any time and create a new one. This allows your application to be able to reconstruct the window more easily.

The one flaw with this is the possibility of having two Graphics objects at once. And that involves dealing with OpenGL contexts. Since an OpenGL context is global state, having multiple Graphics objects could cause problems, since both would have their own contexts. You would need some way to set a particular Graphics object as the current one, which would bind itself as the current OpenGL context (and retrieve any function pointers it needs to).

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • As for the VBO manager, it's really designed with the idea of only creating a single buffer and storing data for multiple different objects in it. I understand having multiple meshes pointing to a single VBO, but eventually somewhere you're going to ideally want to be able to check to see if the data you'd like to push to some (new?) buffer already exists and just point to that, rather than having duplicate data wasting memory. The idea behind the single VBO is to avoid rebinding, which is a murderous operation on mobile. –  Nov 18 '11 at 04:05
  • Good information in the bit about the GLUtils class. Like I said I'm new so I wasn't sure if things like this would be appropriate as simply globally existing somewhere. –  Nov 18 '11 at 04:06
  • @AscensionSystems: That doesn't explain why it is a *Singleton*. IE: an object which you can only create one of. If you want to do this kind of management, that's great. But there's no reason to make this manager a global and to ensure that only one instance of the object can be created. – Nicol Bolas Nov 18 '11 at 04:09
  • yeah that's a good point. I suppose it really boils down to having the global access available to the various display object types I have available to my display list... which would of course be the incredibly lazy and anti-oop way of doing things. lol Didn't even consider that until now and it's so obvious. I should really just hand off an instance to my stage object (which is the root of my display list setup). Thanks for slapping my upside the head with some logic that is so obvious I'm embarrassed now lol –  Nov 18 '11 at 04:14
  • Had a lot of good info from all answers but as I said in my above comment, you made me re-evaluate some of my (bad) assumptions and see what I should have done in the first place. Next time I do a project I'm going to plan it first, not just wing it. :D –  Nov 18 '11 at 17:16
4

Global variables < singletons < object-oriented design patterns

Singletons are not terrible, there's just always a better way. The problem with them really starts to manifest on large projects with several people - if you're using them for a small little personal project then don't feel like you need to restructure your entire design just to avoid them.

Pubby
  • 51,882
  • 13
  • 139
  • 180
3

I've heard the design pattern "singleton" actually referred to as an anti-pattern before, and I think that's a interesting thought.

While it can be incredibly useful, if overused it does basically end up making your code c-like in that it makes everything accessible everywhere (it turns your class into a global). If you have a lot of public methods in your singleton classes, you can pretty much have all the actions of your program reachable from all parts of the program.

This is almost never good object oriented design. While it may work in your head for you, if you allow others to modify your code they'll start to realize - hey, why should I do it this way when I can just directly call that?

Over time the program will probably degrade and become somewhat like spaghetti logic.

Sometimes even when there is only ever going to be one instance of something, it's better to have that instance contained within a higher level entity as a normal member. If you make it so that that higher level entity is a singleton (which even that I don't necessarily recommend), you're pretty much guaranteeing that there will only be one instance of its members. And who knows? Maybe someone will find a reason to have more than one of some of your classes that you haven't thought of yet.

The most important thing is that it is conceptually easy to follow and that your design encapsulates your data well.

Having said that, I've programmed in places where the usefulness of making things globally accessible as singletons seemed worth losing the good OO design of the program. It's a judgement thing for you at the end of the day. If you're starting to make too many singletons though, think strongly about other ways of designing it first that would have more traditional object oriented layouts - I'd bet that 9/10 times you'll find one of those other designs is better than having the singletons.

John Humphreys
  • 37,047
  • 37
  • 155
  • 255
  • I never really understood... if singletons are an anti-pattern, then how is logging, standard I/O, etc. supposed to work? – user541686 Nov 18 '11 at 03:47
  • Yeah I agree with a lot of what you're saying here I just couldn't put together something that is both logical and fits well with the requirements of these objects. I mean if you read the descriptions of the objects that I've made singletons, I'm not sure that it makes much sense to NOT have them globally available. The only "solution" I could come up with would be to convert these back to regular classes and have a single "core" singleton which holds instances to these classes. Engine::Core::getInstance().loadTexture() or whatever. –  Nov 18 '11 at 03:48
  • It's not stating that you should never use a singleton. But if my main() spawns off one active class that contains one logger in it, and that class starts off the rest of my program then that class can be the gateway for accessing the logger. Theres no need to make that logger globally accessible necessarily. Though, I'll admit, logging is one of the few things I do usually aim to make a singleton. This is more talking from experience - I've made the mistake of using too many singletons and it does degrade your OO design like you wouldn't believe, haha. – John Humphreys Nov 18 '11 at 03:49
  • I'll visit this tomorrow, I've gotta get to bed :) I'm hoping someone else will comment more by then so we have a bigger discussion about it. I think this is the sort of debate that's best done with a big group with alot of experience, and there are definitely more experienced people on here than I :) – John Humphreys Nov 18 '11 at 03:54
  • 3
    @Mehrdad: "I never really understood... if singletons are an anti-pattern, then how is logging, standard I/O, etc. supposed to work?" Why does a log need to be a singleton? `cin` and `cout` are not singletons; they're global variables. A Singleton is an object that you *cannot* create more than one of. If you couldn't make more than one `ofstream`, then it would be a Singleton. There is a difference between "Singleton" and "Global" (Singletons are global, but not all globals are singletons). – Nicol Bolas Nov 18 '11 at 04:06
  • @NicolBolas: Sorry, I wasn't thinking of `cin` and `cout`, although I realize this is about C++ -- I was thinking more of classes like `__ConsoleStream` in Microsoft .NET, which are private and hence which cannot be instantiated from outside, but which are nevertheless singletons because they are exposed (through a `Stream` superclass) as `Console.In` and `Console.Out`... or are private classes exempted? – user541686 Nov 18 '11 at 05:58
  • I don't think ConsoleStream is a singleton at all - it may be a class where you may have only one instance alive... and I'm not even sure that's true depending on how many things contain it. Either way, it's sealed so you can't inherit from it, and its internal so it can't be referenced outside it's assembly - but theres nothing stopping things from inside the assembly from creating more than one of it as far as I see. It has an internal constructor which I bet could be called by something inside the assembly more than once if desired. – John Humphreys Nov 18 '11 at 14:20
  • Anyway, I agree that the situation would be good for a singleton - but again, sometimes it's better to just make a normal class and have it contained within a higher level class you're only going to use once. Then you can access it through that class with a real interface and have a good non-globals design. BTW: here's the implementation of consolestream if you wanted to look at it: http://www.123aspx.com/Rotor/RotorSrc.aspx?rot=42958 – John Humphreys Nov 18 '11 at 14:21
3

I also think you are abusing Singletons. Suggestions for the examples you enumerated:

VBOManager

Managing the VBOs is only of interest to the classes that hold 3D meshes. Convert this to private or protected class variables and members in the Mesh class, so that only the meshes have access to this functionality.

TextureManager

Same idea as the VBOs. This should only be visible to texture instances, so this should be in the Texture class as class members/variables.

GLUtils

This one I'm not sure, because it isn't clear to me how it works. Does this even need to be an object? If this is just a collection of common graphics functions, then can it just be a bunch of functions under some namespace?

Graphics

You say this is the initialization code for window, viewport, etc. Do you need to call methods into this object once the application is initialized? Seems your main() function needs to have one of these to set the proper environment for the app to run on, but once that is done you may not need to use this object, so maybe you can just keep it in the scope of main() so that when the app is exiting it can destroy the window and exit cleanly.

InputManager

Not sure you need this one at all. What does it do? You obviously need to have Event objects, but typically these events are sent to the application via some sort of callback, so the app would never need to talk to an InputManager object. Instead, you could have a background thread that listens for events and when one is received it puts it in an event queue that the app reads, or sends it to interested listeners via a callback function.

Miguel Grinberg
  • 65,299
  • 14
  • 133
  • 152
3

I consider singletons a anti-pattern. Let's recall what a singleton is: It'a class of which only one instance can be created of. Thus a singleton is always global. Technically singletons are glorified namespaces. If you'd replace "singleton" with "namespaced set of global variables and associated manipulation functions" you have semantically the same.

There are hardly any uses for singletons.

VBOManager (Singleton) This "manager" is basically responsible for allocating and of course, "managing" vbo's that are used to store texture mapping and vertex coordinates. I control "read/write" through this object so I can cache data written to the vbo by other objects and return pointers (avoiding storing duplicate data like 500 sprites with the same mapping and vertex coordinates).

VBOs are tied to OpenGL contexts but also may be shared between OpenGL contexts. So it makes sense to have multiple VBOManager instances, one for each OpenGL context, or set of sharing contexts.

TextureManager (Singleton, self explanatory)

The same like VBOs. Textures are tied to the context, but may be shared.

GLUtils (Singleton) I pretty much use this for unifying common GL calls which are different based on the current platform, like GL or GLES. Example, GLU functions (libglu isn't on android so I have a custom implementation)

This definitely calls for a namespace. However wouldn't it make more sense to encapsulate functionality in classes like GLUTesselator, GLUQuadric, etc. and allow for multiple instances?

Graphics (Singleton)

You can have multiple windows and contexts.

InputManager (Singleton, self explanatory)

Input may come from multiple sources, each window may generate it. And in a networked game you also deal with multiple input channels.

It may seem like, that a game has only one world/map loaded, or one input system. But would there be harm if you could load multiple worlds/maps? Think about it: Being able to keep multiple maps around allows for much more things, like rendering a dynamic skybox from another map, load the next map in the background and then at the right place just swap a pointer.

Personally I don't see any reason for having singletons.

datenwolf
  • 159,371
  • 13
  • 185
  • 298
1

My experience is that singletons are either explicitly declared everywhere or that they are implied by the business case. Personally I prefer them because it makes it very clear when I use someone else code how it is supposed to work.

Might vote - stick with them - you are doing a good thing.

Adrian Cornish
  • 23,227
  • 13
  • 61
  • 77
1

It depends. 0) As others said, singleton (or better to say, global state) make sense if they represent something that exists only once (eg. filesystem, but is it realy only one?)

But why make it object? (And not global function/variable)

1) encapsulate - if there are meaningfull invariants that must be maintained, its good to make it accesible only through functions that maintain it. If it is just lot of independednt variables, making it object does not help enything and you can acces them directly.

2) inheritance - if you want to use different implementation if eg. configuration says so, you can make factory return some subclass. You can do it without objects, but then you will probubly reinvent virtual functions.

In some cases its better to use "effective" singleton - because you need only one, you use only one, but if you start needing more, you can have more. So in "general" parts of code, instances should be passed to functions, so it is not locked to default one and in "business" code use "singletons" as long as you need just one.

Alpedar
  • 1,314
  • 1
  • 8
  • 12