Sign in to follow this  

Unity Easy global variable management in C++

This topic is 2660 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi there,

I posted a question about global variable declaration and externing in this thread yesterday (you don't need to read it):

http://www.gamedev.net/community/forums/topic.asp?topic_id=580798

Basically, I have been making the transition from standard C to C++ over the past few days and was pulling my hair out over some of the differences between the two languages. Specifically, I was very annoyed at the fact that declaring global variables in C++ is not as simple as it is in C.

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. Depending on which compiler you use and the parameters you pass to it, C++ handles it in a different manner. In most cases, a C++ compiler will complain when it encounters a variable that has already been declared in a particular scope, which happens if you include a main header in your entry point .cpp file that lists your global variables.

Solving this problem is usually either an annoying case of externing every variable whenever need to use it in a different file or by including a header file in your source code that lists all of the externs to global variables that have been declared in your program. Some compilers can get around this annoying hastle depending on the way they put the program together and the parameters you pass to it, but this is not really necessary as I will demonstrate.

In either case, you need to type more than you should, and in an ideal world, you would only need to declare your global variables once and leave it at that.

After tinkering around for a little while, I figured out a very simple way of doing this:

1) Before you include your main header file in your entry point file, insert this:

#define GLOBAL


From there, you would insert your main header file as usual:

#include "game.hpp"


2) Once you have that define in place, open your main header file or the header file where you declare your global variables (I use one called "data.hpp" which is included in my game's main header, for instance). Insert the following code before you declare your global variables:

#ifndef GLOBAL
#define DATA extern
#else
#undef GLOBAL
#undef DATA
#define DATA
#endif


From there, all you simply need to do is prefix all of your global variable declarations with DATA (or whatever term you want to use). For example:

DATA bool active;
DATA string title;
DATA Game* game;


And that's it! Now you don't need to manually extern everything, which saves a lot of work.

To explain how it works, basically the compiler will understand that it needs to declare variables as data for your program's entry point file and use them as externs for everything else. To the compiler, the entry point of the application is telling it that DATA represents white space, which is exactly the same as declaring a variable as you normally would. Every other source code file in the program interprets DATA as extern, so your global variables are only declared once.

I hope this is helpful to you guys!
- Sig

Share this post


Link to post
Share on other sites
Unless I've missed something, you've just gone from prefixing your variables with "extern" to prefixing them with "DATA". One might argue that the former is better since any person looking at the code for the first time will know what "extern" means but be unsure of "DATA".

As a side note, global variables tend to be frowned upon in many cases in "proper" C++.

Share this post


Link to post
Share on other sites
Quote:
Original post by px
Unless I've missed something, you've just gone from prefixing your variables with "extern" to prefixing them with "DATA". One might argue that the former is better since any person looking at the code for the first time will know what "extern" means but be unsure of "DATA".


I'm pretty sure that sticking extern in front of your global variables when they haven't been declared doesn't declare them as global variables, right?

Quote:
Original post by px
As a side note, global variables tend to be frowned upon in many cases in "proper" C++.


Shouldn't be a problem if no one else is reading your code :)

Share this post


Link to post
Share on other sites
Quote:
Original post by Nypyren

// Globals.h
struct Globals
{
// all of your globals here
// constructor, if you want.
};

extern Globals globals;


//Globals.cpp
Globals globals;


I used this method for a while, to be honest it is rather annoying to prefix everything with globals.

Share this post


Link to post
Share on other sites
extern declares a global variable. Without extern it's a definition.

In any case, there isn't much point to creating a technique to ease use of master headers. In the long run it's more important to learn to properly organize your headers and source files. C++ compile times are bad enough without inviting spurious recompiles.

Share this post


Link to post
Share on other sites
I think the real question is, what are you using these globals for? In proper C++ you'd be trying to make everything a bit more object oriented. You'd place most the data you can into some classes that handle said data. You, pass objects around instead of referring to global state. And any global things that you really only need or want one of, you stick into class instances at global scope like Nypyren mentioned.

Using globals is just asking for trouble in the general case.
1) If anyone else uses the code, there is a chance your global state will get read/modified when you didn't expect it. And that is hard to track down.
2) It isn't thread safe, and due to zero encapsulation, point #1 is escalated. Anyone who wants to modify the global state has to know that X variable can only be touched after grabbing mutex Y. Properly encapsulating it into classes lets you create getters/setters/automatic locks/etc to validate that you are properly accessing the data. And, the better choice is to not even have this global state if you are using threads.

Share this post


Link to post
Share on other sites
Alright, so what is the best method of using this "global" data? Is it ok to simply just declare your data when you need it in a particular scope and leave it at that? Or does having a file full of externs suffice (assuming this is good practice)?

Share this post


Link to post
Share on other sites
Define your variables in the smallest possible scope (possibly introducing an artificial scope with curly brackets if it makes sense), and declare them only from locations that require their use.

If you follow this rule of thumb, you'll virtually never create a true global, because you'll have all of your data in local or object scopes 99% of the time.

Share this post


Link to post
Share on other sites
As SiCrane said you *declare* global data in the header (this is where "extern" comes in), and you *define* global data in the C++ source file. Declarations say "Hey compiler, here's a symbol I'm going to use. Assume its good for now, and resolve it fully later on when I define it."

Two observations:
1) Most generally, reliance on globals is frowned upon in all languages, not just C++. If your application has many globals, then it is most-probably poorly or lazily designed.
2) More pointedly, the fact that you apparently have so many globals (and are likely intent on adding more) to the point that you are seeking out machinery to make it "easier" says to me that you are currently inclined to embrace such poor practices. Whether this is due to ignorance of better ways or stubborness on your part I can't judge, but neither is a valid justification.

This is not meant as a slight against you, as you may simply not know any better or perhaps you recieved poor/undirected instruction. The point I and others are trying to make, however, is that reducing or eliminating your reliance on global state causes the redundancy you complain about to vanish, along with many other benefits.

There are times and places for global state to be sure -- Sometimes because it is "correct" and sometimes because it is impractical to retrofit old code with new, wide-scale features (which is one possible justification out of a handful of "lazy solutions" that are sometimes reasonable to make.)

To give you some idea of how little globals can be used, I believe I've used perhaps 1 in the last 50-100 thousand lines of code I've written (ignoring hackish little throw-away tools, where their simple convenience and small scope of the code -- usually just a couple files -- make the drawbacks virtually non-existant) -- and I later refactored in such a way that even that was no longer global.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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;
vs
static 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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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).

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Quote:
Original post by rip-off
C++ and C use the same rules for global variables.

Not really. C contains some frankly bizarre rules regarding globals. One of which that the OP seems to be relying on is the concept of tentative definitions. In C the following is legal at global scope:

int i;
int i;

This creates one definition for i. This is further complicated by the fact that some compilers, for example some versions of gcc, place globals with only tentative definitions into the common segment of object files, which causes all but one to be discarded at link time. The closest C++ analog to this behavior is a static function variable in an inline function. AFAICT C's linkage rules for globals are supposed to be the same as C++'s but I'm not as familiar with the C standard as I am the C++ standard.

Share this post


Link to post
Share on other sites
I really don't see what is the point of this technique. Declare the variable in one header, make sure it says extern, and in the cpp file actually define it (i.e. without extern).

Share this post


Link to post
Share on other sites
Quote:

DATA bool active;
DATA string title;
DATA Game* game;


Other than global state being evil in a variety of ways, naming globals with short, cute names a bad idea. Not only is "active" an incredibly vague variable name, it becomes increasingly so when you put it into global state without any context at all as to how it is used. With title, WHAT is the title referring to? I understand "game" is probably your game state, but it isn't exactly clear what that is.

A good rule to follow is that the larger the scope of a variable the more descriptive its name should be. It's probably even worth it to have a prefix for global variables simply because they should be rare and you really do want to underscore when you are manipulating global state.

Don't be afraid to write things like: g_GameTimerIsActive, g_OperatingSystemWindowTitle, g_GameStateManager

Remember, it doesn't cost you anything to write out a few extra words to describe the variable. You don't pay per character or anything. Be descriptive.

With that said, NONE of these things are a good candidate for a global variable.


[Edited by - M2tM on August 30, 2010 7:44:31 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
Quote:
Original post by rip-off
C++ and C use the same rules for global variables.

Not really. C contains some frankly bizarre rules regarding globals. One of which that the OP seems to be relying on is the concept of tentative definitions. In C the following is legal at global scope:

int i;
int i;

This creates one definition for i. This is further complicated by the fact that some compilers, for example some versions of gcc, place globals with only tentative definitions into the common segment of object files, which causes all but one to be discarded at link time. The closest C++ analog to this behavior is a static function variable in an inline function. AFAICT C's linkage rules for globals are supposed to be the same as C++'s but I'm not as familiar with the C standard as I am the C++ standard.

Very interesting. Though not entirely surprising, C is a bit of a minefield of weird legacy stuff. Maybe it would have been better to say that "idiomatic" C uses the same rules, but C is often the home of extremely idiosyncratic styles.

Share this post


Link to post
Share on other sites
I learnt of this exact trick 12-13 years ago. I think I used it in one Uni assignment but that's it.
It's not a real-world useful thing as globals are mostly very rare, and it doesn't save typing, it just confuses others.

It also encourages you to put variables in header files that might best only be global within that cpp file, i.e. preferably in an unnamed namespace or static. Thus now they are exposed to a lot more of your program which makes tracking down where they're used a bit harder.

Overall, it solves the wrong problem.

Share this post


Link to post
Share on other sites

This topic is 2660 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this  

  • Forum Statistics

    • Total Topics
      628652
    • Total Posts
      2984053
  • Similar Content

    • By arash khalaqhdoust
      hey guys i hope you doing all well. last night i released my first game in google app store, i really appreciate you guys  to download it. and share your reviews about it
      the idea of game comes from mini hackgame of Bioshock.
       link of download:
      https://play.google.com/store/apps/details?id=com.RVBinary.piperist
      many thanks
    • By ForgedInteractive
      Who We Are
      We are Forged Interactive, a small team of like-minded game developers with the sole purpose of making games we love! Currently, we're progressing very quickly with our first project and there are plenty of opportunities and work for new interested programmers. With this project, our development platform is Unity 5.5.2 and C# as our behavioral language. Since this project is our first release, the game itself is a smaller project though progress is moving quickly. We are looking to finalize the current project and get started on future projects in the near future and are expanding our team to do so.
       
      Who We Are Looking For:
      Programmer Level Designer  
      About the Game
      Ours is the tale of two siblings, thrown into a world of chaos. Living in the shadow of their parents' heroic deeds and their Uncle's colorful military career, Finn and Atia are about to become the next force to shape our world. How will you rise through the ranks of Hereilla and what will be your legacy? Once defeated your enemies turn coat and join you in your adventures. Players can enjoy a range of troops and abilities based on their gameplay style which become more important as maps introduce more challenging terrain, enemies and bosses. Strong orc knights, dangerous shamans, and even a dragon are out on the prowl. Knowing when to fight and when to run, and how to manage your army is essential. Your actions alone decide the fate of this world.
       
      Previous Work by Team
      Although we are working towards our first game as Forged Interactive, our team members themselves have worked on titles including and not limited to:
      Final Fantasy Kingsglaive FIFA 2017 Xcom 2 Civilization  
      What do we expect?
      Reference work or portfolio. Examples what have you already done and what projects you have worked on academic or otherwise. The ability to commit to the project on a regular basis. If you are going on a two-week trip, we don't mind, but it would be good if you could commit 10+ hours to the project each week. Willingness to work with a royalty based compensation model, you will be paid when the game launches. Openness to learning new tools and techniques
       
      What can we offer?
      Continuous support and availability from our side. You have the ability to give design input, and creative say in the development of the game. Shown in credits on websites, in-game and more. Insight and contacts from within the Industry.
       
      Contact
      If you are interested in knowing more or joining, please email or PM us on Skype. A member of our management team will reply to you within 48 hours.
       
      E-mail: Recruitment@ForgedInteractive.com
      Skype: ForgedInteractive
       
      Regards,
      David, Colin and Joseph
       
      Follow us on:
      Facebook: https://www.facebook.com/ForgedInteractive/
      Twitter: @ForgedInteract
      Youtube: https://www.youtube.com/channel/UCpK3zhq5ToOeDpdI0Eik-Ug?view_as=subscriber
      Reddit: www.reddit.com/user/Forged_Interactive

    • By dell96
      I'm trying to make my first project but I'm stuck i don't know how to make my crate to start to spawn again when i hit the start button after i die.
      hoping someone can help!!!
      Crate.cs
      CrateSpawn.cs
      Cratework.cs
      GameController.cs
      GameManager.cs
  • Popular Now