Easy global variable management in C++

Started by
23 comments, last by iMalc 13 years, 7 months ago
Thanks for your help guys. I'm wondering why declaring values as globals is considered bad practice, though. Does it have an effect on performance or is it more of a good coding practice?
Advertisement
I already tried to mention a few reasons above. But off the top of my head:

1) No access control. Anyone, anywhere, can modify or inspect a global. This is bad, as it leads to hard to track down bugs.

2) Not thread safe. Because of #1, it can be hard to be sure everyone is properly locking the data. This makes for even harder to track down bugs.

3) Order of initialization problem. For any non-pod type, it will have a constructor called in the order the object appears in the file. But, between files, there is no standard way to guarentee that foo.cpp's constructors are run before bar.cpp's constructors. So you can't be sure things init in the order you want them to.

4) Not memory friendly. Accessing a global means reading some random memory location. Due to cache locality being a big factor in application speed, you want to keep from accessing random locations. You want to access locations in-order (array) or in recently used order (the stack) or in related to order (by grouping into a class/struct object) to insure it is in the cache. You also need to watch out for false-aliasing, and that is really hard to do with a global, as the global is very likely to randomly evict some piece of data you need from the cache.

5) Not optimization friendly. The smaller the scope in which you define your variables, the better chance the compiler is going to have to optimize the use of the variable. The is especially important considering the compiler may not have a perfect global optimizer, and so most optimizations take place on a single cpp file at a time. In the case of a global, it knows (unless you put static on it) that you could have called extern for it somewhere else in code, and so it can't optimize away the variable.
So, if at global scope you have:
int myNumber = 15;vsstatic int myNumber = 15;

For the first one, the compiler, even if it sees you NEVER called "myNumber = n" in this .cpp file, can't remove myNumber. In the second case, however, if you never said "myNumber = n" then the compiler will treat it the same as
static const int myNumber = 15;
and it will "fold" the 15 into your code, and "myNumber" ceases to exist in the final code.
Well if global variables have a performance impact then that's a good enough reason for me not to use them.
Gonna play devil's advocate for a moment and give a good use for globals ;P


Used sparingly it will give you access to a classes internal stuctures. Case in point, I have my hud code in one class, my INI parser in another. I tell the hud to create a new hud from the parser with default parameters. I take that hud and parse in each needed variable directly into the hud structure, no middle stucture involved. All I need is the number of the blank hud entry and direct access to the hud data. The advantage is I only fill in the hud data that is actually present in the INI file.

BTW, you can create a common global file for your externs by making a .inc file then including it where needed.

******************************************************************************************
Youtube Channel

Quote:Original post by Sigvatr
Well if global variables have a performance impact then that's a good enough reason for me not to use them.


That should be the least of your reasons. The impact of globals on the overall architecture and design of your code is far more important than a trivial microsecond here and there. You will waste literally trillions of times more time debugging and trying to understand badly written code than you will lose on slightly-less-than-optimal code, over the course of your lifetime.



Quote:Original post by LancerSolurus
Used sparingly it will give you access to a classes internal stuctures. Case in point, I have my hud code in one class, my INI parser in another. I tell the hud to create a new hud from the parser with default parameters. I take that hud and parse in each needed variable directly into the hud structure, no middle stucture involved. All I need is the number of the blank hud entry and direct access to the hud data. The advantage is I only fill in the hud data that is actually present in the INI file.


A much superior alternative would be to pass an INI parser instance to the constructor of the HUD wrapper class. This allows you to do things like configure different HUDs based on different INI files (yayy for per-user settings). It also lets you do things like decouple the INI parser from the INI format (just write a "configuration parser" interface and implement that interface from your INI class, XML class, JSON class, whatever) so that you can configure the HUD from different data sources, even including manual input from a debug console or something similar. Neither of those two is nearly as practical when using a global.

Access to a class's internals is bad. You should be designing your code so that you minimize the dependence between unrelated chunks of code, not increase it. Hide details behind interfaces whenever possible - this improves flexibility, increases modularity and reusability, decreases the amount of code that a bug can negatively influence, makes code easier to test and verify, and generally makes your architecture cleaner and simpler to maintain.


Quote:BTW, you can create a common global file for your externs by making a .inc file then including it where needed.


Uh... what? What does this do that using an .h file in the same role does not?

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Well for one I tend to write integrated routines, as in routines that function as a small part of the whole. The reason I chose the INI parser is a perfect example of why I chose to use globals. When parsing I do not have the whole section, I get it a single line at a time. So I create a blank entry, then fill in each part as the parser gets to that line. In this case I have no idea what the next line will contain, but as long as it belongs to the same hud section it will get added to the correct hud entry.

As you have said though, the more you can encapsulate your classes the better, it builds for long term reuse. I mainly use it when I build one class onto another and want the two classes to act as one.

If you can do it entirely OOP method that is the best for reusability, but you do sacrifice speed in certain cases.

******************************************************************************************
Youtube Channel

Quote:Sigvatr
Basically, I have been making the transition from standard C to C++ over the past few days

[...]
Quote:
#define


You can't make a transition from C to C++ in a few days, as above quotes from your OP prove to every halfway experienced C++ programmer.

Not only are global variables frowned upon in C++, but also #define-macros. The latter don't play fine C++, as:

* they don't have scope (they are simply there)
* they can't be used as template arguments in themselves
* they can change meaning of code far away in the compilation unit
* macro expansion can yield sub-performant or even buggy and dangerous code
* not typesafe

In more than 999 ‰ of cases, C++ has better builtin tools (in picogen, which weighs roughly 50kLoC at the moment, I have less than 50LoC of macro code, i.e. less than 1 ‰ overall, hence my claim; and that macro code is only to print out some compiler-id at runtime).
Quote:Original post by LancerSolurus
Well for one I tend to write integrated routines, as in routines that function as a small part of the whole. The reason I chose the INI parser is a perfect example of why I chose to use globals. When parsing I do not have the whole section, I get it a single line at a time. So I create a blank entry, then fill in each part as the parser gets to that line. In this case I have no idea what the next line will contain, but as long as it belongs to the same hud section it will get added to the correct hud entry.


This sounds like a blatant shortcoming of your INI parser; why not just load the whole file at once, and parse it into sections of key/value pairs? Could be done with a std::map<std::string, std::map<std::string, std::string> > trivially, with a templated wrapper function for typesafe conversion from the internal value string to the desired type using lexical_cast or stringstreams.

In any case, I really honestly fail to see how this adds up to an argument for global variables. If anything, it's a classic demonstration of how reliance on globals has crippled your code feature-wise and made it harder to maintain!


Quote:As you have said though, the more you can encapsulate your classes the better, it builds for long term reuse.


I said interfaces, not classes. My recommendations hold for functional, procedural, or even declarative programs just as much as for object-oriented ones.


Quote:I mainly use it when I build one class onto another and want the two classes to act as one.


Not sure what you mean by this, but it sounds fishy to me. Why do you have two classes that you want to treat as a single class? Why aren't you using inheritance, composition, or simply refactoring into a single class here?


Quote:If you can do it entirely OOP method that is the best for reusability, but you do sacrifice speed in certain cases.


Uh, no.

Please, show me some profiling where well-written OO code performs notably worse than the equivalent procedural/functional/otherwise-non-OO code.

This is superstition at best, and outright misinformation at worst.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Quote:
In standard C, you can simply declare your global variables in your program's main header (assuming you use one) and leave it at that.

C++ and C use the same rules for global variables. Any difference you are seeing stems from your misunderstanding of how C's compiling and linking stages actually work. It is possible that the code appears to be working how you described, but unless you are externing in the header file and defining in a single source file you are doing it wrong.
Quote:
... the more you can encapsulate your classes the better, it builds for long term reuse.

Your classes are coupled, not encapsulated. Coupling actually makes it harder to re-use code.

Quote:
If you can do it entirely OOP method that is the best for reusability, but you do sacrifice speed in certain cases.

I hope your INI parser isn't designed in that manner as an "optimisation". The bottleneck in such code is certainly going to be the disk access, not what happens next.

This topic is closed to new replies.

Advertisement