Baruch
Baruch

Reputation: 21548

Global variables (again)

I keep hearing that global variables should never be used, but I have a tendency to dismiss "never" rules as hot-headed. Are there really no exceptions?
For instance, I am currently writing a small game in c++ with SDL. It seems to me to make a lot of sense to have a global variable with a pointer to the screen buffer, because all the different class that represent the different type of things in the game will need to blit to it, and there is only one screen buffer.

Please tell me if I am right that there are exceptions, or if not then:

(I would assume that this question had been asked on SO before, however couldn't find what I need (an explanation and workaround) when searching. If someone could just post a link to a previous question, that could be great)

Upvotes: 2

Views: 1854

Answers (11)

Stack Overflow is garbage
Stack Overflow is garbage

Reputation: 248279

Of course there are exceptions. I personally can't think of a single situation where a goto is the right solution (or where a singleton is the right solution), but global variables occasionally have their uses. But... you haven't found a valid excuse.

Most objects in your game do not, repeat, not need to access the screen buffer. That is the responsibility of the renderer and no one else. You don't want your logger, input manager, AI or anyone else putting random garbage on the screen.

And that is why people say "don't use globals". It's not because globals are some kind of ultimate evil, but because if we don't say this, people fall into the trap you're in, of "yeah but that rule doesn't apply to me, right? I need everything to have access to X". No, you need to learn to structure your program.

More common exceptions are for state-less or static objects, like a logger, or perhaps your app's configuration: things that are either read-only or write-only, and which truly needs to be accessible from everywhere. Every line of code may potentially need to write a log message. So a logger is a fair candidate for making global. But 99% of your code should not even need to know that a screen buffer exists.

The problem with globals is, in a nutshell, that they violate encapsulation: Code that depends on a global is less reusable. I can take the exact same class you're using, put it in my app, and it'll break. Because I don't have the same network of global objects that it depends on.

It also makes the code harder to reason about. What value will a function f(x) return? It obviously depends on what x is. But if I pass the same x twice, will I get the same result? If it uses a lot of globals, then probably not. Then it becomes really difficult to just figure out what it's going to return, and also what else it is going to do. Is it going to set some global variable that's going to affect other, seemingly unrelated, functions?

How can this be achieved, preferably without having to pass it to every constructor to be stored internally until needed

You make it sound like that's a bad thing. If an object needs to know about the screen buffer, then you should give it the screen buffer. Either in the constructor, or in a later call. (And it has a nice bonus: it alerts you if your design is sloppy. If you have 500 classes that need to use the screen buffer, then you have to pass it to 500 constructors. That's painful, and so it's a wake-up call: I am doing something wrong. That many object shouldn't need to know about the screen buffer. How can I fix this?`)

As a more obvious example, say I want to calculate the cosine of 1.42, so I pass 1.42 to the function: cos(1.42)

That's how we usually do it, with no globals. Of course, we could instead say "yeah but everyone needs to be able to set the argument to cos, I'd better make it global". Then it'd look like this:

gVal = 1.42;
cos();

I don't know about you, but I think the first version was more readable.

Upvotes: 7

Puppy
Puppy

Reputation: 147036

What if you want to update your engine to support dual screen? Multiple displays are becoming more and more common all the time. Or what if you want to introduce threading? Bang. How about if you want to support more than one rendering subsystem? Whoopsie. I want to pack my code as a library for other people or myself to re-use? Crap.

Another problem is that the order of global init between source files is undefined, making it tricky to maintain more than a couple.

Ultimately, you should have one and only one object that can work with the screen buffer - the rendering object. Thus, the screen buffer pointer should be part of that object.

I agree with you from a fundamental point of view - "never" is inaccurate. Every function call you make is calling a global variable - the address of that function. This is especially true for imported functions like OS functions. There are other things that you simply cannot unglobal, even if you wanted to - like the heap. However, this is most assuredly not the right place to use a global.

The biggest problem with globals is that if you later decide that a global wasn't the right thing to do for any reason (and there are many reasons), then they're absolutely hell to factor out of an existing progam. The simple fact is that using a global is just not thinking. I can't be bothered to design an actual rendering subsystem and object, so I'm just gonna chuck this stuff in a global. It's easy, it's simple, and not doing this was the biggest revolution in software programming, ever, and for good reason.

Make a rendering class. Put the pointer in there. Use a member function. Problem solved.

Edit: I re-read your OP. The problem here is that you've split your responsibilities. Each class (bitmap, text, whatever) should NOT render itself. It should just hold the data that the master rendering object needs to render it. It's a Bitmap's job to represent a bitmap - not to render a bitmap.

Upvotes: 3

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145457

"Why not": global variables give you spaghetti information flow.

That's the same as goto gives you spaghetti control flow.

You don't know where anything comes from, or what can be assumed at any point. The INTERCAL solution of introducing a come from statement, while offering some initial hope of finally being sure of where control comes from, turned out to not really solve that problem for goto. Similarly, more modern language features for tracking updates to global variables, like onchangeby, have not turned out to solve that problem for global variables.

Cheers & hth.,

Upvotes: 1

Clifford
Clifford

Reputation: 93566

In this case a class that provides member functions for all methods that need access to the screen buffer would be a more OOP friendly approach. Why should everyone and any one have uncontrolled access to it!?

As to whether there are times when a global is better or even necessary, probably not. They are deceptively attractive when you are hacking out some code, because you need jump through no syntactic hoops to access it, but it is generally indicative of poor design, and one that will rapidly atrophy inter maintenance and extension.

Here's a good read on the subject (related to embedded programming, but the points apply to any code, it is just that some embedded programmers thing they have a valid excuse).

Upvotes: 0

DoXicK
DoXicK

Reputation: 4812

The reason i never do it is because it creates a mess. Imagine setting ALL unique variables to globals, you would have an external list the size of a phonebook.

Another reason could be that you don't know where it is initialized or modified. What if you accidently modify it at place X in file Y? You will never know. What if it isn't initialized yet? You will have to check everytime.

if (global_var = 0) // uh oh :-(
if (object->Instance() = 0) // compile error :-)

This can both be fixed using singletons. You simply cant assign to a function returning you the object's adress.

Besides that: you don't need your screen buffer everywhere in your application, however if you want to: go ahead, it doesn't make the program run less good :-)

And then you still have the namespace problem but that at least gives you compile errors ;-)

Upvotes: 1

Steve Jessop
Steve Jessop

Reputation: 279445

If the screen buffer is shared between lots of different pieces of code, then you have two options:

1) Pass it around all over the place. This is inconvenient, because every piece of code that uses the screen buffer, even indirectly, needs to be laboriously indicated as such by the fact that this object is passed through the call stack.

2) Use a global. If you do this, then for all you know any function at all in your entire program might use the screen buffer, just by grabbing it from the global[*]. So if you need to reason about the state of the screen buffer, then you need to include the entire program in your reasoning. If only there was some way to indicate which functions modify the screen buffer, and which cannot possibly ever do so. Oh, hang on a second...

This is even aside from the benefits of dependency injection - when testing, and in future iterations of your program, it might be useful for the caller of some function that blits, to be able to say where it should blit to, not necessarily the screen.

The same issues apply as much to singletons as they do to other modifiable globals.

You could perhaps even make a case that it should cost you something to add yet another piece of code that modifies the screen buffer, because you should try to write systems which are loosely coupled, and doing so will naturally result in fairly few pieces of code that need to know anything at all about the screen in order to do their job (even if they know that they're manipulating images, they needn't necessarily care whether those images are in the screen buffer, or some back buffer, or some completely unrelated buffer that's nothing to do with the screen). I'm not actually in favour of making extra work just to punish myself into writing better code, but it's certainly true that globals make it quite easy to add yet another inappropriate wad of coupling to my app.

[*] Well, you may be able to narrow it down on the basis that only TUs that include the relevant header file will have the declaration. There's nothing technically to stop them copy-and-pasting it, but in a code base that's at all well regulated, they won't.

Upvotes: 2

Jeffrey Hantin
Jeffrey Hantin

Reputation: 36534

Global variables (and singletons, which are just a wrapper around a global variable) can cause a number of problems, as I discussed in this answer.

For this specific issue -- blittable objects in a game kit -- I'd be apt to suggest a method signature like Sprite::drawOn(Canvas&, const Point&). It shouldn't be excessive overhead to pass a reference to the Canvas around, since it's not likely to be needed except in the paint pathway, and within that pathway you're probably iterating over a collection anyway, so passing it in that loop isn't that hard. By doing this, you're hiding that the main program has only one active screen buffer from the sprite classes, and therefore making it less likely to create a dependency on this fact.

Disclaimer: I haven't used SDL itself before, but I wrote a simple cross-platform C++ game kit back in the late '90s. At the time I was working on my game kit, it was fairly common practice for multi-player X11-based games to run as a single process on one machine that opened a connection to each player's display, which would quite efficiently make a mess of code that assumed the screen buffer was a singleton.

Upvotes: 0

Zack Bloom
Zack Bloom

Reputation: 8427

Like any other design decision, using global variables has a cost. It saves you having to pass variables unnecessarily, and allows you to share state among running functions. But it also has the potential to make your code hard to follow, and to reuse.

Some applications, like embedded systems, use global variables regularly. For them, the added speed of not having to pass the variable or even a pointer into the activation record, and the simplicity, makes it a good decision [arguably]. But their code suffers for it; it is often hard to follow execution and developing systems with increasing complexity becomes more and more difficult.

In a large system, consisting of heterogeneous components, using globals may become a nightmare to maintain. At some point you may need a different screen buffer with different properties, or the screen buffer may not be available until it's initialized meaning you'll have to wrap every call to it with a check if it's null, or you'll need to write multithreaded code, and the global will require a lock.

In short, you are free to use global vars while your application is small enough to manage. When it starts to grow, they will become a liability, and will either require refactoring to remove, or will cripple the programs growth (in terms of capability or stability). The admonition not to use them stems from years of hard-learned lessons, not programmer "hot-headedness".

Upvotes: 7

Leonid
Leonid

Reputation: 23500

  • Global variables can change in unexpected ways, which is usually not what you want. The state of the application will become complex and unmaintainable. Very easy to make something wrong. Especially if someone else is changing your code;

  • Singleton might be a better idea. That would at least give you some encapsulation in case you need to make extensions in the future.

  • One reason not to use global variables is a problem with namespaces (i.e. accidentally using the same name twice);

  • We do often use global (to namespace) constants at work which is considered normal, as they don't change (in unexpected ways) and it is very convenient to have them available in multiple files.

Upvotes: 2

JOTN
JOTN

Reputation: 6317

We tell students never to use global variables because it encourages better programming methods. It's the same reason we tell them not to use a goto statement. Once you're an accomplished programmer then you can break the rules because you should know when it's appropriate.

Upvotes: 8

suszterpatt
suszterpatt

Reputation: 8303

I'm also curious to hear the precise explanation, but I can tell you that the Singleton pattern usually works pretty well to fill the role of global variables.

Upvotes: -2

Related Questions