Appropriate use of global const variables in C++?
Asked Answered
F

4

6

I am working on a program for my CS class. It is a simulation of a delivery company's activities at an airport.

This is a very simple, small program consisting of a few header and source files and a main.cpp source file that orchestrates the simulation.

There are certain given constant values, such as the frequency of shipment arrival, the load capacity of planes, the amount of time it takes a worker to process certain items, etc. (all are integer values). It is necessary for me to access these variables throughout several functions in main.cpp

It seemed reasonable to declare these above the main() function as const ints, effectively making them global, e.g.

const int kTotalTime = 2000;
const int kPlaneCapacity = 25;
int main(){//...program code}

I am aware that global variables are to be avoided in most situations, as there are no restrictions on where they can be called and/or modified, which can lead to accidentally breaking parts of the program which in turn may be difficult to debug, as well as causing compatibility issues for future code, etc. However since these are read-only values of a primitive data type, which are used throughout the program, it seemed like a reasonable solution. Also, it makes an explicit statement about the purpose of the variables to anyone reading the code as well as to the compiler.

Questions: Is my logic flawed? How so? When are global variables (const or not) reasonable to use? If this is a bad solution, then how would you suggest declaring constant read-only values such as these?

Thank you very much for your time!

Forsyth answered 19/9, 2011 at 6:34 Comment(0)
G
5

Regarding the size and purpose of your program (as I understand it from your description) it probably doesn't matter, but since it has an educational context, I'd suggest to "do it right".

In such a situation I would go for a Config struct (or class, if you want to make it a bit smarter, see below) which carries the configuration values and can be tossed around your program. It has the advantage that you can easily change it if you have to, say, fetch your options from a file or from the command line.

As for the class versus struct thingy (note that I am making a logical distinction here, not a technical). Either you just put all values as members in your struct and pass around const refs of it, or you make it a full fledged class with accessors that hide where the data is coming from (and how it is generated). Programming is always decision making and this is your decision to make. If you think you will have to allow more configuration possibilities in the future (like mentioned above) you may want to go for class abstraction.

Yet another option is to scatter your data across your program, which is actually a lot smarter than it sounds. If every class knows only its configuration options (and hides them) you can actually make use of the OOP language, you're using. Example:

// footype.h
class FooType {
  private:
    static const int fooOption;
};
// bartype.h
class BarType {
  private:
    static const float barOption;
};

The question is, how to initialise this. One way could be to create a config.cpp that looks like this:

#include "footype.h"
#include "bartype.h"

const int FooType::fooOption = 42;
const float BarType::barOption = 7.4;

So you have information hiding, and you still have all the config options together at one place (config.cpp).

Edit:

If you have config option that is required by many (more than one) different modules, you can go for a bit of sophistication (with indirection) like so:

// footype.h
class FooType {
  private:
    static const int& fooOption;
    static const bool& dumpLevel;
};
// bartype.h
class BarType {
  private:
    static const float& barOption;
    static const bool& dumpLevel;
};

config.cpp:

#include "footype.h"
#include "bartype.h"

static const int opt_foo = 42;
static const float opt_bar = 7.4;
static const bool opt_dumpLevel = false;

const int& FooType::fooOption = opt_foo;
const bool& FooType::dumpLevel = opt_dumpLevel;
const float& BarType::barOption = opt_bar;
const bool& BarType::dumpLevel = opt_dumpLevel;

You can even make the options non-const if you want (but I don't see the point in a configuration option that is mutable).

Gormley answered 19/9, 2011 at 6:52 Comment(9)
There is almost no difference between a class and a struct.Erie
@Arafangion: There is a logical difference, as I pointed out. Besides the fact that there is also a technical difference (struct members are public by default, while class members are private by default). The technical difference hints you to the logical: structs are considered plain and stupid containers of data by most people while classes are considered smart objects that have directly associated operations.Gormley
bitmask, I like your idea of a config struct or class, that seems like a cleaner and more organized solution and as you said, facilitates easy modification of the values in case the user wants to modify the data. I think I will choose this route after weighing the pros and cons of class vs. struct for this task. As far as scattering data across the program, let's say I have a class Item and within it I'm describing a few properties that are universally accessible and that every instance of the class will share, can I just make a public: static const int (for ex.) or private w/ accessor? T.Y.Forsyth
Pardon my ignorance, but what is the advantage of the second method (with indirection) as opposed to the first? Also, would it be unreasonable to make those variables public members as opposed to using accessors?Forsyth
In the last question of my previous comment, my misunderstanding is solely based on the fact that we're using constant variables, so they already cannot be modified, thus it appears that making them public would simplify retrieval without compromising the data, could you please help me understand the problem with that?Forsyth
The point is that public data members are strongly discouraged, unless you have very very very good reasons (which you don't). The advantage of the second version is that you specify the value only once (when defining and declaring the static variables in config.cpp). Then you relay that information to the type members. The reference is not required, you can just as well make rigid copies of those values (I just included that for clarity and so that large, non-pod constants are not copied, but for const (!) bools and ints you are better off without making the members references.Gormley
@Gormley One more question before I select yours as the accepted answer - for the second version, what would be the proper way to write the accessor in order to return the references to the variable? would it be simply: declaration static int GetFooOption() implementation: static int GetFooOption(){ return fooOption; } I'm used to passing values by reference, for example as parameters to a function e.g. MyFunction(list<Foo> &f) but the technique you wrote had me slightly confused (also, it's 4:30am where I am at the moment) so thanks for your patience!Forsyth
disregard my last question, the getter will of course be: static const int& GetFooOption(); const int& GetFooOption() { return fooOption; }Forsyth
@ObjectiveCat: Both would do (assuming you go for the Config class). However, if you won't have really complex types and you want to keep the type open for future variations, I think I'd go for non-reference return values, because to return a reference, you need a variable that holds the value. If your Config implementation happens to fetch values at runtime from a file you would have to buffer them in local members.Gormley
C
5

I think it's best to put your constants as static inside the class.

I assume you have the class Plane, just do this:

Plane.h

class Plane{
   static const int kPlaneCapacity;
   //....
}

Plane.cpp

const int Plane::kPlaneCapacity = 25;

Also, take good care of what you understand by constant. Pi is a constant. 10 is a constant. I do see how you would think a plane capacity is constant, but think about this: What if your teacher says that for your next assignment, your plane capacity should be 30, and not 25.

Cotto answered 19/9, 2011 at 6:42 Comment(2)
By constant I mean values that will never be modified by the program, but I want them to visually stand out to the person who is reading the code, therefor they follow the kConstantVariable naming convention and are listed at the top of the main.cpp file. I am probably going to follow bitmask's advice and make a config class, thus isolating the variables. I actually already have a few class-specific constants defined statically inside the class. For example, my Event class has various conststatic kEventType. Is it reasonable to simply make them public? Or is the use of an accessor recommended?Forsyth
Thank you for the suggestion! (ran out of space in previous comment).Forsyth
G
5

Regarding the size and purpose of your program (as I understand it from your description) it probably doesn't matter, but since it has an educational context, I'd suggest to "do it right".

In such a situation I would go for a Config struct (or class, if you want to make it a bit smarter, see below) which carries the configuration values and can be tossed around your program. It has the advantage that you can easily change it if you have to, say, fetch your options from a file or from the command line.

As for the class versus struct thingy (note that I am making a logical distinction here, not a technical). Either you just put all values as members in your struct and pass around const refs of it, or you make it a full fledged class with accessors that hide where the data is coming from (and how it is generated). Programming is always decision making and this is your decision to make. If you think you will have to allow more configuration possibilities in the future (like mentioned above) you may want to go for class abstraction.

Yet another option is to scatter your data across your program, which is actually a lot smarter than it sounds. If every class knows only its configuration options (and hides them) you can actually make use of the OOP language, you're using. Example:

// footype.h
class FooType {
  private:
    static const int fooOption;
};
// bartype.h
class BarType {
  private:
    static const float barOption;
};

The question is, how to initialise this. One way could be to create a config.cpp that looks like this:

#include "footype.h"
#include "bartype.h"

const int FooType::fooOption = 42;
const float BarType::barOption = 7.4;

So you have information hiding, and you still have all the config options together at one place (config.cpp).

Edit:

If you have config option that is required by many (more than one) different modules, you can go for a bit of sophistication (with indirection) like so:

// footype.h
class FooType {
  private:
    static const int& fooOption;
    static const bool& dumpLevel;
};
// bartype.h
class BarType {
  private:
    static const float& barOption;
    static const bool& dumpLevel;
};

config.cpp:

#include "footype.h"
#include "bartype.h"

static const int opt_foo = 42;
static const float opt_bar = 7.4;
static const bool opt_dumpLevel = false;

const int& FooType::fooOption = opt_foo;
const bool& FooType::dumpLevel = opt_dumpLevel;
const float& BarType::barOption = opt_bar;
const bool& BarType::dumpLevel = opt_dumpLevel;

You can even make the options non-const if you want (but I don't see the point in a configuration option that is mutable).

Gormley answered 19/9, 2011 at 6:52 Comment(9)
There is almost no difference between a class and a struct.Erie
@Arafangion: There is a logical difference, as I pointed out. Besides the fact that there is also a technical difference (struct members are public by default, while class members are private by default). The technical difference hints you to the logical: structs are considered plain and stupid containers of data by most people while classes are considered smart objects that have directly associated operations.Gormley
bitmask, I like your idea of a config struct or class, that seems like a cleaner and more organized solution and as you said, facilitates easy modification of the values in case the user wants to modify the data. I think I will choose this route after weighing the pros and cons of class vs. struct for this task. As far as scattering data across the program, let's say I have a class Item and within it I'm describing a few properties that are universally accessible and that every instance of the class will share, can I just make a public: static const int (for ex.) or private w/ accessor? T.Y.Forsyth
Pardon my ignorance, but what is the advantage of the second method (with indirection) as opposed to the first? Also, would it be unreasonable to make those variables public members as opposed to using accessors?Forsyth
In the last question of my previous comment, my misunderstanding is solely based on the fact that we're using constant variables, so they already cannot be modified, thus it appears that making them public would simplify retrieval without compromising the data, could you please help me understand the problem with that?Forsyth
The point is that public data members are strongly discouraged, unless you have very very very good reasons (which you don't). The advantage of the second version is that you specify the value only once (when defining and declaring the static variables in config.cpp). Then you relay that information to the type members. The reference is not required, you can just as well make rigid copies of those values (I just included that for clarity and so that large, non-pod constants are not copied, but for const (!) bools and ints you are better off without making the members references.Gormley
@Gormley One more question before I select yours as the accepted answer - for the second version, what would be the proper way to write the accessor in order to return the references to the variable? would it be simply: declaration static int GetFooOption() implementation: static int GetFooOption(){ return fooOption; } I'm used to passing values by reference, for example as parameters to a function e.g. MyFunction(list<Foo> &f) but the technique you wrote had me slightly confused (also, it's 4:30am where I am at the moment) so thanks for your patience!Forsyth
disregard my last question, the getter will of course be: static const int& GetFooOption(); const int& GetFooOption() { return fooOption; }Forsyth
@ObjectiveCat: Both would do (assuming you go for the Config class). However, if you won't have really complex types and you want to keep the type open for future variations, I think I'd go for non-reference return values, because to return a reference, you need a variable that holds the value. If your Config implementation happens to fetch values at runtime from a file you would have to buffer them in local members.Gormley
C
3

When are global variables (const or not) reasonable to use?

If your program is a multithreaded program then you should be giving a serious thought about using globals for they would need proper synchronization to avoid race conditions. Usually proper synchronization is not a very trivial task and requires some serious undrestanding and thought.

Here is an excerpt from a nice article:

Non-locality -- Source code is easiest to understand when the scope of its individual elements are limited. Global variables can be read or modified by any part of the program, making it difficult to remember or reason about every possible use. No Access Control or Constraint Checking -- A global variable can be get or set by any part of the program, and any rules regarding its use can be easily broken or forgotten.

Implicit coupling -- A program with many global variables often has tight couplings between some of those variables, and couplings between variables and functions. Grouping coupled items into cohesive units usually leads to better programs.

Memory allocation issues -- Some environments have memory allocation schemes that make allocation of globals tricky. This is especially true in languages where "constructors" have side-effects other than allocation (because, in that case, you can express unsafe situations where two globals mutually depend on one another). Also, when dynamically linking modules, it can be unclear whether different libraries have their own instances of globals or whether the globals are shared.

Testing and Confinement - source that utilizes globals is somewhat more difficult to test because one cannot readily set up a 'clean' environment between runs. More generally, source that utilizes global services of any sort that aren't explicitly provided to that source is difficult to test for the same reason.

Given all the above, As long as you understand the pitfalls and understand that the way you are using the globals does insulate your program against those pitfalls, then you can go ahead and very well use globals.

Cameroncameroon answered 19/9, 2011 at 6:37 Comment(6)
Very nice article, but totally irrelevant to constant values.Sinful
Thank you for the interesting points and article, the program I am working on is single threaded and quite basic, but once I venture into the multithreading realm, I will keep your advice in mind. Also, I'd like to accentuate the fact that I am asking specifically about using const globals, which in my (humble, yet limited) opinion narrows the scope of issues normally associated with globals.Forsyth
@bitmask: I have added the OP's Question const or not at the top of the answer.Cameroncameroon
@ObjectiveCat: True, with const globals the issue of multi threaded is nt relevant. Apart from that there are quite a few relevant issues as the article mentions, but i don't think those apply for your program and that is why i mentioned in the answer as long as you are aware of the pitfalls you can go ahead and have those globals.Cameroncameroon
@Als: Thank you sir! Just to clarify, despite my specific program dealing with constants, I am also interested in the general ramifications of using globals, as I haven't come across much information on the topic (hence my question), and I appreciate the broad perspective. The only thing I have heard in my college courses is that globals are bad due to their lack of scope (obvious).Forsyth
@ObjectiveCat: Glad I could help, All the Best :)Cameroncameroon
M
0

Arduino code is an example of when it is appropriate to use different types of global variables.

Every Arduino sketch contains a setup() function which is run once at startup, and a loop() function which is run repeatedly. Anything declared in setup() is unavailable in loop(). So instantiated classes (e.g. MqttClient) need to be global.

Similarly, anything declared in loop() is redeclared in the next iteration of loop(). Some sensors take a few milliseconds to poll. Polling some sensors causes them to heat up. Because of all that, Arduino sketches will often have multiple actions being performed at different times.

An example using a temperature sensor: you may poll it and print its value to the terminal every 4 seconds, print the value to a LCD screen every 10 seconds, and publish the value to a MQTT broker every 20 seconds.

It is bad form to use delay() in loop() because that is a blocking function which prevent the CPU from doing anything else. So timers are used instead:

if( ( millis() - lastPollTime ) > pollInterval )
{
  tempC = bme280.readTemperature();
  lastPollTime = millis();
}

To store my lastPollTime without using a non-const global variable, I would need to use a class¹ which keeps track of the last-run time in an instance variable. That's a lot of overhead for what otherwise takes only two lines of code, so my code has one global unsigned long for each unique timer I need to use.

And since I may not use the tempC variable for another thousand iterations of loop(), it too needs to be a non-const global variable.

¹Found one while writing this answer: https://github.com/contrem/arduino-timer

Myoglobin answered 13/3 at 20:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.