views:

233

answers:

10

I know and understand that global variables and magic numbers are things to avoid when programming, particularly as the amount of code in your project grows. I however can't think of a good way to go about avoiding both.

Say I have a pre-determined variable representing the screen width, and the value is needed in multiple files. I could do...

doSomethingWithValue(1920);

But that's a magic number. But to avoid that, I'd do...

const int SCREEN_WIDTH = 1920;

//In a later file...
extern const int SCREEN_WIDTH;
doSomethingWithValue(SCREEN_WIDTH);

And now I'm using a global variable. What's the solution here?

+10  A: 

In your second example, SCREEN_WIDTH isn't really a variable1, it's a named constant. There is nothing wrong with using a named constant at all.

In C, you might want to use an enum if it's an integer constant because a const object isn't a constant. In C++, the use of a const object like you have in the original question is preferred, because in C++ a const object is a constant.

1. Technically, yes, it's a "variable," but that name isn't really "correct" since it never changes.

James McNellis
So global variables are fine, as long as they're declared const?
Lewis
As usual, you shouldn't have golden immutable rules, you should know the reasons behind the best pratices to understand if they apply to your case.
Matteo Italia
@Lewis, the named constant can be appropriately scoped using braces "{}". Your code snippet does not indicate where the named constant is declared. Put it at the highest scope necessary, but not higher.
@Lewis: that is one way to look at it, yes. The trouble with variable global variables is knowing which bits of the code modify them and when and why. If the 'variables' are constant, there isn't a problem with other code modifying them; they always have the same meaning for everything. So they are a way of sharing common information - and quite effective and relatively innocuous.
Jonathan Leffler
Why not use an enum (instead of #define) in C? It shows up more readily in the debugger, if nothing else.
Jonathan Leffler
@Lewis: One exception is if you have a const global object that has a complex dynamic initialization (in C++), since that can get messy.
James McNellis
@Jonathan: Because I forgot that enumerators can appear in integral constant expressions in C. Thank you for reminding me. :-)
James McNellis
But is using const thread-safe compared to using enum or define?
Nyan
@Nyan: An immutable object is thread-safe. As long as an object doesn't change, there is no opportunity for a race condition.
James McNellis
+1  A: 

The main problem with global variables is when they're non-const. Non-changing globals aren't nearly such a concern as you always know their value anywhere they're used.

In this case, one sane approach is to create a constants namespace and put the constant values there, for reference anywhere in your program. This is much like your second example.

Mark B
+1  A: 

They have to be defined somewhere. Why not put the defines in a .h file or in a build file?

Preet Sangha
+2  A: 

Why do you need to hard-code the screen width in the first place? Where does it come from? In most real applications, it comes from some system API,which tells you which resolution you're currently running, or which resolutions the system is capable of displaying.

Then you just take that value and pass it to wherever it is needed.

In short, on this line: doSomethingWithValue(SCREEN_WIDTH); you're already doing it. SCREEN_WIDTH might be a global in this particular example, but it doesn't have to be, because the function isn't accessing it as a global. You're passing the value to the function at runtime, so what the function sees isn't a global variable, it's just a plain function argument.

Another important point is that there's typically nothing wrong with immutable global data.

Global constants are typically fine. The problem occurs when you have mutable global state: objects that can be accessed throughout all of the application, and which might have a different value depending on when you look. That makes it hard to reason about, and causes a number of problems.

But global constants are safe. Take for example pi. It is a mathematical constant, and there's no harm in letting every function see that pi is 3.1415..... because that's what it is, and it's not going to change.

if the screen width is a hard-coded constant (as in your example), then it too can be a global without causing havoc. (although for obvious reasons, it probably shouldn't be a constant in the first place9

jalf
+1  A: 

It's not a variable, it's constant, which is resolved at compile time.

Anyhow, if you don't like such a "floating" constant, you could put it in a namespace or whatever to collect all the constants of that type together. In some cases you may also consider an enum to group related constants.

Even better, if this can apply to your situation, avoid using a fixed predetermined screen width and use the correct APIs to retrieve it at runtime.

Matteo Italia
+1  A: 

If a global is a must then it's typically a good idea to wrap it in a function and use the function to get the value.

Another post that might help

skimobear
How is this a good idea?
mathepic
A misread question on my part. I was referring to global variables and the question was about a const value. Apologies.
skimobear
+5  A: 

I would recommend defining the constant within a namespace in a header file. Then the scope isn't global and you don't need to redefine it (even with the extern keyword) in multiple places.

Eric Mickelsen
+1  A: 

While the global in your second case is a fairly innocuous one, unless you're designing this for something where you're sure the screen width won't change, I'd use something to obtain the screen width dynamically (e.g., GetSystemMetrics on Windows, or XDisplayWidth on X).

Jerry Coffin
+1  A: 

One way to avoid this would be to set up your program as an object and have a property on the object (my_prog.screen_width). To run your program, have main() instantiate the object and call a ->go method on the object.

Java does this. A lot. Half-decent idea.

Bonus features for your program's expansion:

  • When you make your program customizable some day, you can have it set the property in the constructor instead of recompiling everything.
  • When you want to run two instances of your program next to each other in the same process, they can have different settings.

It's not a huge deal for a quick one-off program, though.

fennec
A: 

It's important to recognize that even global constants can sometimes cause problems if they need to be changed in future. In some cases, you may decide that you simply aren't going to let them change, but other times things may not be so easy. For example, your program may contain a graphic image which is sized to match the screen; what happens if it needs to be adjusted to run on a different-sized screen? Merely changing the named constant won't necessarily fix the graphic image that is embedded in the program. What if it needs to be able to decide at run-time what size screen to use?

It's not possible to deal with every conceivable contingency, and one shouldn't try too hard to protect against things that simply Aren't Going To Happen. Nonetheless, one should be mindful of what things can change, so as to avoid boxing oneself into a corner.

When designing a class, having a virtual readonly property which returns an unchanging value will allow future extensions of the class to return different values. Using a property rather than a constant may have some performance implications, but in some cases the flexibility will be worth it. It might be nice if there were a way to define virtual constants, and I see no reason it wouldn't be theoretically possible for .net to allow it, (include the constant values in the table with virtual method pointers) but so far as I know it doesn't.

supercat