Acceptable use of globals in this case?

Started by
19 comments, last by Oberon_Command 15 years, 6 months ago
For a number of years, I've had a codebase which I've been using on my projects. Basically, it's a collection of classes which provide access to graphics methods, resource management, configuration file parsing, etc... However, recently I decided that the system I currently had was not flexible enough, so I decided to restructure it. My past system was more or less a layer on top of SDL, especially for rendering. While I do enjoy programming in SDL, and it meets my needs quite nicely for most things, I do plan on upgrading my code-base to support OpenGL at some point in time. The new code-base (called BaseCode2 since I have nothing better to call it :P) implements a system where each of the API-dependent layers are abstract interface classes. The primary video subsystem, for instance, is a class called "VideoScreen", which has all virtual members. The intent behind this is that I write a "driver" for each new API that I want to add to the codebase, and the user of the codebase (usually me) uses the interface ONLY to access those functions. Initializing an instance of the "VideoScreen" class might look something like this:

VideoScreen *screen = new SDLVideoScreen();
screen->Initialize(640,480, SDL_ANYFORMAT);

Now, this system is up and running and currently works quite well; all I really need to do to switch "drivers" is write a new implementation of VideoScreen, say "OGLVideoScreen", and point the "screen" pointer to an instance of that class. This way, the rest of the program should care minimally about how "VideoScreen"s are implemented. Now, here's my problem: my last version of "BaseCode" implemented only ONE version of the VideoScreen class, and it was a singleton. This made things easier throughout the program, since I didn't have to explicitly declare a global variable and I didn't have to explicitly pass a VideoScreen pointer to every method that needed to use it. I'm not certain as to how that might work with the new system, however; actually, I can't see how it would work at all. I can use global variables for this, but I'd really rather not. My question is this: would it be more acceptable to use global variables in this case instead of trying to get a singleton working? Is it even possible to use a "VideoScreen" singleton in this case, where everything (else) about the singleton is purely virtual and is overloaded by a derived class implementation?
Advertisement
Quote:Original post by Oberon_Command
My question is this: would it be more acceptable to use global variables in this case instead of trying to get a singleton working? Is it even possible to use a "VideoScreen" singleton in this case, where everything (else) about the singleton is purely virtual and is overloaded by a derived class implementation?
Either a global variable or a singleton indicates that the VideoScreen will be visible globally, but I would have a hard time justifying this. Why does the Screen class need to be globally visible? Or in other words: why does every element of the program need access to the screen? If not every element needs access to the screen (and I can't see why, for instance, the audio system would need the screen), then it should not be globally visible, and neither global variables nor singletons are appropriate.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Quote:Original post by swiftcoder
Quote:Original post by Oberon_Command
My question is this: would it be more acceptable to use global variables in this case instead of trying to get a singleton working? Is it even possible to use a "VideoScreen" singleton in this case, where everything (else) about the singleton is purely virtual and is overloaded by a derived class implementation?
Either a global variable or a singleton indicates that the VideoScreen will be visible globally, but I would have a hard time justifying this. Why does the Screen class need to be globally visible? Or in other words: why does every element of the program need access to the screen? If not every element needs access to the screen (and I can't see why, for instance, the audio system would need the screen), then it should not be globally visible, and neither global variables nor singletons are appropriate.


The idea is that it would be visible only to video output subsystems ("Renderer" objects).

Would it be acceptable to put the variable in a particular header file and only include that header file in the source files that needed it? I admit that it's not a particularly elegant way of doing things, but I'm stumped as to what else I could do without destroying the readability of all the classes that use the VideoScreen functionality. I.e.:

// I would prefer to do thispixelRenderer->DrawAt(x,y,colour);// as opposed to thispixelRenderer->DrawAt(screen, x, y, colour);// or even thispixelRenderer->setScreen(screen);pixelRenderer->DrawAt(x,y,colour);

Quote:Original post by Oberon_Command
The idea is that it would be visible only to video output subsystems ("Renderer" objects).

Would it be acceptable to put the variable in a particular header file and only include that header file in the source files that needed it? I admit that it's not a particularly elegant way of doing things, but I'm stumped as to what else I could do without destroying the readability of all the classes that use the VideoScreen functionality.
What is wrong with passing the screen to the constructor?

pixelRenderer = new PixelRenderer(screen);pixelRenderer->DrawAt(x, y, colour);

In general, this is the preferred method to eliminate global variables/singletons - which should be avoided wherever possible.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Quote:Original post by swiftcoder
Quote:Original post by Oberon_Command
The idea is that it would be visible only to video output subsystems ("Renderer" objects).

Would it be acceptable to put the variable in a particular header file and only include that header file in the source files that needed it? I admit that it's not a particularly elegant way of doing things, but I'm stumped as to what else I could do without destroying the readability of all the classes that use the VideoScreen functionality.
What is wrong with passing the screen to the constructor?

*** Source Snippet Removed ***


Mainly because in the final product the VideoScreen would get passed down through a chain of constructors that would look something like this:

main() // initialized here-> GameApp--> RenderManager---> PixelRenderer


Here's how it used to be, before I turned VideoScreen into a singleton:
main() // initialized here-> GameApp--> GamePlayState---> GameMap----> Tile-----> Image // FINALLY gets used here


The first chain is certainly better than the second... but I'd still like to avoid that sort of thing as much as possible. If I were to use this system of referencing, RenderManager would have to be passed down similar chains in place of the Screen. Actually, that was probably going to happen anyway.

If I have to do that in both cases, I will, but it doesn't strike me as stylistically "clean". Granted, neither does the use of globals... To refine my question, I guess what I'm asking is whether there's a way to make a particular instance of an object global to only a few objects instead of the whole program without trickling a reference to that object through several layers of classes.
Lower level components must at some point be populated with state that was given to them by higher level components. I don't see anything wrong with the screen trickling down through constructors, in fact it does strike me as being clean.
Quote:Original post by Oberon_Command
Mainly because in the final product the VideoScreen would get passed down through a chain of constructors that would look something like this:

main() // initialized here-> GameApp--> RenderManager---> PixelRenderer


Here's how it used to be, before I turned VideoScreen into a singleton:
main() // initialized here-> GameApp--> GamePlayState---> GameMap----> Tile-----> Image // FINALLY gets used here


The first chain is certainly better than the second... but I'd still like to avoid that sort of thing as much as possible. If I were to use this system of referencing, RenderManager would have to be passed down similar chains in place of the Screen. Actually, that was probably going to happen anyway.

Why is such a chain a bad thing? It makes it clear when reading the code exactly where the screen comes from. And just as importantly, it means I can swap it out easily, if I need to test the Tile class, say, with a fake screen object.
Quote:Original post by Spoonbender
Quote:Original post by Oberon_Command
Mainly because in the final product the VideoScreen would get passed down through a chain of constructors that would look something like this:

main() // initialized here-> GameApp--> RenderManager---> PixelRenderer


Here's how it used to be, before I turned VideoScreen into a singleton:
main() // initialized here-> GameApp--> GamePlayState---> GameMap----> Tile-----> Image // FINALLY gets used here


The first chain is certainly better than the second... but I'd still like to avoid that sort of thing as much as possible. If I were to use this system of referencing, RenderManager would have to be passed down similar chains in place of the Screen. Actually, that was probably going to happen anyway.

Why is such a chain a bad thing? It makes it clear when reading the code exactly where the screen comes from. And just as importantly, it means I can swap it out easily, if I need to test the Tile class, say, with a fake screen object.


I'm not sure. It just seems ugly to me. What I'd ideally like is something like a singleton (one instance only) that can only be accessed by classes that declare themselves able to use it. So, something perhaps like this:
class Tile uses VideoScreen{...


But I'm using C++, and I don't think that would be useful for anything but singletons, so I'll just use the "trickle-down" method. It does have the advantages you mention.

Thanks, all.
Quote:Original post by Spoonbender
Why is such a chain a bad thing? It makes it clear when reading the code exactly where the screen comes from.


These contructors could be called from many higher up constructor sites, so infact it is not clear at all where it came from.

You need to have knowledge of the entire chain (something not visible when examining the source of any of these constructors) to know whats really going on.

The only thing clear is that the object was handed to it. This does infact have some utility, but that is not what you seem to have meant.

This sort of reference spaghetti can be almost as evil as GoTo, but probably not so much in this case. Globals can also be evil, but also not so much in this case.

The global/singleton solution has value because you only ned to know one thing to understand the entire system: There can be only one.
Quote:Original post by Rockoon1
Quote:Original post by Spoonbender
Why is such a chain a bad thing? It makes it clear when reading the code exactly where the screen comes from.


These contructors could be called from many higher up constructor sites, so infact it is not clear at all where it came from.

You need to have knowledge of the entire chain (something not visible when examining the source of any of these constructors) to know whats really going on.

The only thing clear is that the object was handed to it. This does infact have some utility, but that is not what you seem to have meant.

This sort of reference spaghetti can be almost as evil as GoTo, but probably not so much in this case. Globals can also be evil, but also not so much in this case.

The global/singleton solution has value because you only ned to know one thing to understand the entire system: There can be only one.


This sounds more like an argument against deep class hierarchies than reference passing -- introducing globals only hides the problem that the tree is unwieldy and opaque to understanding. You don't understand the entire system, you only understand the global -- and since anything can modify globals, you probably only think you understand it! (and what's happening to it)

This topic is closed to new replies.

Advertisement