Search

6.9 — Sharing global constants across multiple files (using 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

Prior to C++17, the following is the easiest and most common solution:

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 and the scope resolution operator)
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. This is because the compiler needs to know the value of the variable at compile time, and a forward declaration does not provide this information.

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 in each code file where constants.h is #included, and all uses of these constants will be linked to the version instantiated 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). In other files, the compiler will only see the forward declaration, which doesn’t define a constant value (and must be resolved by the linker). This means in other files, these are treated as runtime constant values, not compile-time constants. Thus outside of constants.cpp, these variables can’t be used anywhere that requires a compile-time constant. Second, because compile-time constants can typically be optimized more than runtime constants, the compiler may not be able to optimize these as much.

Key insight

In order for variables to be usable in compile-time contexts, such as array sizes, the compiler has to see the variable’s definition (not just a forward declaration).

Because the compiler compiles each source file individually, it can only see variable definitions that appear in the source file being compiled (which includes any included headers). For example, variable definitions in constants.cpp are not visible when the compiler compiles main.cpp. For this reason, constexpr variables cannot be separated into header and source file, they have to be defined in the header file.

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 linker will consolidate all inline definitions of a variable into a single variable definition (thus meeting the one definition rule). 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.10 -- Static local variables
Index
6.8 -- Why (non-const) global variables are evil

148 comments to 6.9 — Sharing global constants across multiple files (using inline variables)

  • Inevitable

    What happens if instead of using header we directly copy and paste namespace inside different .cpp files in Inline case and change the variables initialization value so each .cpp file has own initialization? because as mentioned inline definition satisfies ODR but now we have different initialization and which value is used when we are accessing inline variables ?

    • nascardriver

      > we directly copy and paste namespace inside different .cpp
      That's exactly what the preprocessor does with #includes. If you copy the headers manually and change a value or define a variable with the same name and a different value, you get undefined behavior, because all occurrences of an `inline` variables have to be initialized with the same value.

  • Ivan

    Maybe the best way for global variables is to create a separate class and use it everywhere in you code, taking variables via get() methods or directly?

  • Why in the following code file the header file was used? I feel like I've seen this before in the lessons but I can't recall where.

    • Alex

      It's a common best practice for a .cpp file to include the .h of the same name. This allows the compiler to detect certain kinds of mismatches between definitions and declarations.

  • Samarth

    Hey folks,
    I may be reading this wrong or got it wrong but in your point

    "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. "

    1.What do you mean by the first point? Are we allowed to define it multiple times in multiple files? for ex if I define it in constants.h , can I also define it in main.cpp? (but they must have identical definition of course)

    2.Instead of using inline variables that have External Linkage - can I not use the "extern" keyword for constants to make them global and use them in my multiple files?

    I understand the 2nd point (#including the header file will automatically include the definition in the file where we need to use the constant - if I'm not wrong)

  • Waldo Lemmer

    Section "Global constants as inline variables":

    >

    Why do you use snake_case here? Is that preferred for global consts? 4.14 says:

    > Some programmers prefer to use all upper-case names for const variables. Others use normal variable names with a ‘k’ prefix. However, we will use normal variable naming conventions, which is more common. Const variables act exactly like normal variables in every case except that they can not be assigned to, so there’s no particular reason they need to be denoted as special.

  • Hell

    Uhm... Can you give me some clarification? I'm kind of confused.
    So in this lesson you said if we go with inline constexpr variable, we can rewrite the header file "without the downside of duplicated variables".
    But I'm guessing that if you include the header file in every file in the project, when we change the value of an inline constexpr variable in the header file, all the including files will still need recompiling right? So this will still cause lengthy rebuild time just like when you're using (non-inline) constexpr variable right?
    Thanks

    • Alex

      Yes, if you change a value, then you will get a potentially lengthy recompile. As these are supposed to be constants, hopefully you're not changing them too often. :)

  • Little help here.

    Compile time is where the compiler check if you follow certain language like C++ on your code.

    And Run Time is where the linker links objects like forward declaration to its actual definition.

    Is there anything I missed or something is wrong in my insight?

    • nascardriver

      Compile time is when the code is translated into machine language (When the compiler is running).
      Link time is when the linker takes the compiler's output (the compiler generates 1 output file for every source file) and turns it into a single binary.
      Run time is when you run the program (eg. by pressing "start" or double clicking the executable).

      Compile time is when a car's parts are being created.
      Link time is when the car gets assembled.
      Run time is when you drive around in the car.

  • Jacob Christie

    I get the error
         "inline specifier allowed on function declarations only"
    How do I fix this?

Leave a Comment

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