Search

6.8 — Global constants and inline variables

In some applications, certain symbolic constants may need to be used throughout your code (not just in one location). These can include physics or mathematical constants that don’t change (e.g. pi or Avogadro’s number), or application-specific “tuning” values (e.g. friction or gravity coefficients). Instead of redefining these constants in every file that needs them (a violation of the “Don’t Repeat Yourself” rule), it’s better to declare them once in a central location and use them wherever needed. That way, if you ever need to change them, you only need to change them in one place, and those changes can be propagated out.

This lesson discusses the most common ways to do this.

Global constants as internal variables

There are multiple ways to facilitate this within C++. Pre-C++17, the following is probably the easiest and most common:

1) Create a header file to hold these constants
2) Inside this header file, define a namespace (discussed in lesson 6.2 -- User-defined namespaces)
3) Add all your constants inside the namespace (make sure they’re constexpr)
4) #include the header file wherever you need it

For example:

constants.h:

Then use the scope resolution operator (::) with the namespace name to the left, and your variable name to the right in order to access your constants in .cpp files:

main.cpp:

When this header gets #included into a .cpp file, each of these variables defined in the header will be copied into that code file at the point of inclusion. Because these variables live outside of a function, they’re treated as global variables within the file they are included into, which is why you can use them anywhere in that file.

Because const globals have internal linkage, each .cpp file gets an independent version of the global variable that the linker can’t see. In most cases, because these are const, the compiler will simply optimize the variables away.

As an aside...

The term “optimizing away” refers to any process where the compiler optimizes the performance of your program by removing things in a way that doesn’t affect the output of your program. For example, lets say you have some const variable x that’s initialized to value 4. Wherever your code references variable x, the compiler can just replace x with 4 (since x is const, we know it won’t ever change to a different value) and avoid having to create and initialize a variable altogether.

Global constants as external variables

The above method has a few potential downsides.

While this is simple (and fine for smaller programs), every time constants.h gets #included into a different code file, each of these variables is copied into the including code file. Therefore, if constants.h gets included into 20 different code files, each of these variables is duplicated 20 times. Header guards won’t stop this from happening, as they only prevent a header from being included more than once into a single including file, not from being included one time into multiple different code files. This introduces two challenges:
1) Changing a single constant value would require recompiling every file that includes the constants header, which can lead to lengthy rebuild times for larger projects.
2) If the constants are large in size and can’t be optimized away, this can use a lot of memory.

One way to avoid these problems is by turning these constants into external variables, since we can then have a single variable (initialized once) that is shared across all files. In this method, we’ll define the constants in a .cpp file (to ensure the definitions only exist in one place), and put forward declarations in the header (which will be included by other files).

Author's note

We use const instead of constexpr in this method because constexpr variables can’t be forward declared, even if they have external linkage.

constants.cpp:

constants.h:

Use in the code file stays the same:

main.cpp:

Because global symbolic constants should be namespaced (to avoid naming conflicts with other identifiers in the global namespace), the use of a “g_” naming prefix is not necessary.

Now the symbolic constants will get instantiated only once (in constants.cpp), instead of once every time constants.h is #included, and the other uses will simply refer to the version in constants.cpp. Any changes made to constants.cpp will require recompiling only constants.cpp.

However, there are a couple of downsides to this method. First, these constants are now considered compile-time constants only within the file they are actually defined in (constants.cpp), not anywhere else they are used. This means that outside of constants.cpp, they can’t be used anywhere that requires a compile-time constant. Second, the compiler may not be able to optimize these as much.

Given the above downsides, prefer defining your constants in the header file. If you find that for some reason those constants are causing trouble, you can move some or all of them into a .cpp file as needed.

Global constants as inline variables

C++17 introduced a new concept called inline variables. In C++, the term inline has evolved to mean “multiple definitions are allowed”. Thus, an inline variable is one that is allowed to be defined in multiple files without violating the one definition rule. Inline global variables have external linkage by default.

Inline variables have two primary restrictions that must be obeyed:
1) All definitions of the inline variable must be identical (otherwise, undefined behavior will result).
2) The inline variable definition (not a forward declaration) must be present in any file that uses the variable.

The compiler will consolidate all inline definitions into a single variable definition. This allows us to define variables in a header file and have them treated as if there was only one definition in a .cpp file somewhere. These variables also retain their constexpr-ness in all files in which they are included.

With this, we can go back to defining our globals in a header file without the downside of duplicated variables:

constants.h:

main.cpp:

We can include constants.h into as many code files as we want, but these variables will only be instantiated once and shared across all code files.

Best practice

If you need global constants and your compiler is C++17 capable, prefer defining inline constexpr global variables in a header file.


6.9 -- Why global variables are evil
Index
6.7 -- External linkage

29 comments to 6.8 — Global constants and inline variables

  • John

    Hi, great tutorials. I found this line confusing: "The compiler will consolidate all inline definitions into a single variable definition"
    Is it the compiler or linker that does this?

  • Alek

    HEllo,thank you for your great exlanation and valubale information.I noticed you have mistyped something maybe so you can modify it.one paragraph before you start discussing "global constants as inline variables" you've written like this:
    However, there are a couple of downsides to this method. First, these constants are now considered compile-time constants only within the file they are actually defined in (constants.cpp), not anywhere else they are used. This means that outside of constants.cpp, they can’t be used anywhere that requires a compile-time.
      If I'm not wrong you should change first "compile-time constants" to "run-time constants" because you are takling about global constants as external variables.

  • Taras

    I saw in the snippet above we didn't include the header. Earlier we used to include the header file in the source file with definitions. Why here is that omitted?

  • Ian

    After you talked about "Global constants as external variables". You gave a comment:

    However, there are a couple of downsides to this method. First, these constants are now considered compile-time constants only within the file they are actually defined in (constants.cpp), not anywhere else they are used. This means that outside of constants.cpp, they can’t be used anywhere that requires a compile-time constant. Second, the compiler may not be able to optimize these as much.

    Can you give an example of it? Thank you.

    • nascardriver

      Up until now, we only needed compile-time constants in the optional chapter O. If you skipped it, treat this code as a bit of magic, there'll be better examples when you get to arrays.

      The size of a `std::bitset` has to be a compile-time constant. We can pass in `i` and `k`, because the compiler can see their value at compile-time. `j` doesn't work, because it's not constant at all.
      If `i` was defined in a different file, and only forward declared in a header, the compiler wouldn't be able to know the value of `i` at compile-time, because `i` could be initialized with a run-time value. Note that the compiler compiles each file separately, it doesn't look up anything in other files.

  • AliAbdulKareem

    So as far As I understood the inline variable, they are basically making our Variable like-static (initialized only once) with an external linkage?
    and also allowing us to re-define it(even tho, it should be identical?)

  • Ambareesh

    Pre C++17, from the two methods described in the article for global constants, when should one opt for the first and when the second?

    • nascardriver

      If you're concerned about memory usage (Which is unlikely, your constants probably have a tiny memory footprint), go with the non-constexpr method. Otherwise, use `constexpr`.

  • CPPLearner

    Wouldn't a "Constants" namespace be risky to use as other libraries might also use the same namespace?

    Would it make more sense to put the constants namespace in it's own namespace, e.g. thisApp:

    // Wraps all globals in thisApp namespace to avoid collisions
    namespace thisApp
    {
        namespace constant
        {
            inline constexpr double pi { 3.14159 };
        }
    }

    • nascardriver

      Yep. For applications, this isn't a huge problem, as you could simply rename your `constant` namespace. In libraries however, this could be fatal. Libraries should try to use unique names, eg. by using your suggestion of using the library name, and, if applicable, the company name.

  • bissetriz

    Hello Alex and Nascardriver! First of all, I'm very thankfull for your effort in maintaining this tutiorial series. I'm learning a lot! I also have a few questions:

    1. When using inline constexpr global variables we still have the problem of having to re-compile every file that uses the variable when we change its value?

    2. I'm becoming part of a team and they put their constants in a .txt file so they don't need to re-compile any file when updating a constant, is this considered a bad practice?

    Thanks for your time!

    • nascardriver

      Hi!

      1. Yes. Any change to a file causes recompilation.
      2. No, that's fine. You'll want to be able to customize programs. This is only problematic if the values aren't sanitized when reading them. It shouldn't be possible to break the program by modifying the file.

  • Vishal Macom

    1) All definitions of the inline variable must be identical (otherwise, undefined behavior will result).

    I dont undertand this point please give an example if possible.
    because i have seen some functions with different definitions

    • nascardriver

      a.cpp

      b.cpp

      `i` is fine, both definitions are identical.
      `j`'s definitions differ, that causes undefined behavior.

  • Vishal Macom

    Now the symbolic constants will get instantiated only once (in constants.cpp), instead of once every time constants.h is #included, and the other uses will simply refer to the version in constants.cpp. Any changes made to constants.cpp will require recompiling only constants.cpp.

    However, there are a couple of downsides to this method. First, these constants are now considered compile-time constants only within the file they are actually defined in (constants.cpp), not anywhere else they are used. This means that outside of constants.cpp, they can’t be used anywhere that requires a compile-time constant. Second, the compiler may not be able to optimize these as much.

    Could you please elaborate on this point.
    like how you mean about compiling only constants.cpp and no need to compile other file.

    • nascardriver

      Compilers compile all source files individually. When you re-compile a project, the compiler only has to compile the files that have been modified (Or the files that include modified headers).
      When you define variables in a header and you update the value of such a variable, the compiler has to re-compile all files that include this header. If you define the variable in the source file, you only have to modify the source file, so the compiler only has to re-compile that single file.

  • Wambui

    Hi, I have noticed that in some instances of 'int main' for example in this page as well, the '()' has been left out. I am now maybe confused; is it optional is C++11, C++17 and newer or is this a typo?

  • Sirdavos

    if we update header file(constants.h) in Global constants as inline variables, do we need to compile all the files which included this header file ?

  • Fan

    Can we inline non-const variables?

    Suppose getnum() is a function that asks the user for input and returns an int, and we have

    #include'd in multiple files. Does it ask for user input multiple times?

    • nascardriver

      First off, avoid calling functions during construction of global variables. The function you're calling might depend on some other global object, which hasn't been initialized yet.

      You can inline non-const variables, but they'll all have the same value, because there is only 1 variable (despite there being multiple definitions).

  • Prasad

    constants.h:

    #ifndef CONSTANTS_H
    #define CONSTANTS_H

    namespace Constants
    {
        // since the actual variables are inside a namespace, the forward declarations need to be inside a namespace as well
        extern const double pi;
        extern const double avogadro;
        extern const double my_gravity;
    }

    #endif

    should the namespace match the name mentioned in constants.cpp file, Its capitalized in .h file and not in cpp file.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]