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 linker 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

98 comments to 6.8 — Global constants and inline variables

  • Greg

    Hello I have a question. For inline variables, the downside of it needing to recompile all the files when a single value is changed in the header files still stands as compared to using the extern const method am I correct?

  • rishit

    In Global constants as external variable, all your declarations are in constant.h, definitions in constant.cpp. Now, you said that compiler compiles each file individually. While compiling main.cpp, won't the compiler throw an error since it just sees a declaration for const double pi, and not any initialization? In 4.14, you said all const variables have to be initialized when they are declared.

  • Tobito

    when i using this code on header file, it was showing the error "inline specifier allowed on function declarations only"

    inline constexpr int min {1};

  • XiangShuang

    As for "We use const instead of constexpr in this method because constexpr variables can’t be forward declared, even if they have external linkage."
      why cannot constexpr be forward declared? Is it useless or something else?

    • nascardriver

      The compiler compiles each cpp file individually. If you want to use a variable at compile-time (By making it `constexpr`), the compiler has to see the variable's definition. If the definition is in another cpp file, the compiler can't use it.

  • Husen Patel

    Is declaring variables as inline forbidden in C++20? my compiler generated an error, so I had to compile my program with this g++ command and visual studio couldn't compile the program, I used this command:
    g++ -std=c++17 main.cpp , Is this the only way to compile this program or there is another way such as changing something in visual studio settings???

  • Chayim

    What is the "#include constants.h" in the constants.cpp file all about? Why is it needed? What does it do? Is it the CONSTANTS-H file? Why is it needed to include it? The constants.h file only defines a namespace function and has nothing to do with the header file.

    • jdhtzww

      Since constants.h file only contains the forward declarations, constants.h.cpp has the definitions. So the linker will know where to find the definitions.
      (see sec2.11, the paragraph just above title 'Can’t we just avoid definitions in header files?')

      • Chayim

        The linker combines all files in one executive file and does not need anything else to include, once the constants.cpp file and the CONSTANTS_HEADER file are one file because the header file forwards the values from the constants.cpp file that’s all what it needs.

  • Chayim

    "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)."
    —————————————————————
    Why is it needed to put it in a header? we can make a forward declaration only, when needed. A header is only needed when many variables are needed to be forwarded.

  • J34NP3T3R

    QUESTION : does it matter where the inline definition was placed as long as they are identical ?

    say in namespace "constants" we have     inline constexpr double pi { 3.14159 }; // note: now inline constexpr
    then within main() we also have     inline constexpr double pi { 3.14159 }; // note: now inline constexpr

    are they treated as the same variable ?

  • Peter

    Hi, I have a question.
    How to make inline variable undefined?

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

    I love to learn from error codes, I tried and search on the internet, but I don't know how to make it happen.

    I tried like this

  • Hali

    Question, i'm sorry if this question has already been answers, if so please re-directed to the chapter. Why is constexpr can't be forward declared? even if they have external linkage (extern); can you please explain in detail whey it can't be forward like const and non-const variables?

    Thank you.

    • nascardriver

      The compiler compiles 1 file at a time, producing 1 output file for each. The linker later builds the executable out of all output files.

      The compiler has to be able to see a value in order to use it. If a `constexpr` variable would be forward declared, the value would be in a different file and the compiler wouldn't be able to see it.

  • J34NP3T3R

    QUESTION ABOUT INLINE VARIABLES :

    how does it affect the VALUE of the variable in files that uses that variable ?

    if say in 1.cpp you declared     inline int xx{ 2 };
    followed by assignment     xx = 3;

    then say in 2.cpp you declared     inline int xx{ 2 };

    now it changes back to 2 ?

    ALSO can you inline a variable with internal linkage ?

  • J34NP3T3R

    "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 provide an example on ;
    "This means that outside of constants.cpp, they can’t be used anywhere that requires a compile-time constant."

    because the in the first "Downside" we discussed about a workaround that suggests placing the constants on a header file

    but then another "Downside", this time for that workaround suggests placing the constants in the CPP file and the forward declarations in the header file.

    but then there is a downside for this as well, about ;
    "This means that outside of constants.cpp, they can’t be used anywhere that requires a compile-time constant."

    which solution or workaround is safer to use ?  what place requires a compile-time constants where we cant use our run-time constants ( that are considered compile-time constants ) ?

  • goiu

    Hi there!
    I'm back to review some concepts ^^
    I was wondering:
    1) A constexpr global variable can't be forward declared, so it's pointless to give it external linkage. So, if we include the definition of an extern constexpr global variable across multiple files then we are violating the ODR right?
    I've tried this and got no warnings nor errors nor undefined behaviors, I guess it is because the compiler simply optimize away the variable, but let me know if i miss something ?.?
    2) To quote Alex (and maybe also nascardrive :P), "under the hood" exactly how can the compiler prevent the linker from seeing entities with identifier with internal linkage? It replaces directly the memory location of the variable? How does it work?
    Thanks in advance for your answers C:

    • nascardriver

      1) Multiple definitions of an `extern constexpr` variable violate the ODR and cause a linker error. Optimizations should not change this.

      2) The compiler can emit names of entities with internal linkage, but they'll be marked as having internal linkage. The linker won't attempt to connect such entities across different files.

      Given the following program

      [/code]
      // example.cpp
      static int horse_with_long_legs{};
      [/code]

      Using nm to list symbols

      The lower case 'b' means that `horse_with_long_legs` are local. Uppercase 'B' means `tiger_with_fuzzy_fur` is global.

      Your compiler can choose not to emit names of local entities, because they're not needed. Names of local entities are usually emitted in debug mode to ease debugging, but may be missing in release mode.

  • Patrick

    As a test, I tried including a header file that defined a const named x into two other cpp files. I thought I would get a multiple definition error, but that doesn't seem to be the case with constants. Why is that?

    Edit: Is it because the consts have internal linkage?

    • nascardriver

      Yes, that's because `const` variables have internal linkage.
      They're separate variables in each of the cpp files. By also making the variable `inline` in the header, the variable will be shared between the cpp files, without causing a linker error.

  • huroa

    "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."
    I thought that constants don't really need the g_ prefix even if not namespaced because they can't be accidentally changed and can't create spaghetticode

  • kiwikiz

    Is the reason why constexpr variables can't be forward declared because the compiler compiles each file individually, with no knowledge of what is in other files. If not, may i ask the reason why constexpr variables can't be forward declared? Thank you!

    • nascardriver

      Yes that's the reason. The compiler has to see the definition of `constexpr` variables to be able to use them. If they're defined in a different file, the compiler can't see them.

  • TonyCheeze

    So, I understand the case for using inline constexpr variables, as it avoids the duplication that occurs with the internal constexpr variables that we saw in the first method, but don't we still have to recompile every file that #includes the headers with the inline variables?

    I understand that the linker at the end will consolidate all the definitions into a single definition for a given variable, but until that point, the compiler still needs to recompile every file when we change the consts value, right?

    So, if we're trying to reduce build time, say for a really large project, would it be ideal to use the second method you described, wherein we define the const variables in a .cpp file and then forward declare them in a .hpp file? Thanks!

    • nascardriver

      Every file that includes your header has to be recompiled if you update the `constexpr` variable's value. You cannot have a `constexpr` declaration in a header and a definition elsewhere.
      Variables defined as `constexpr` can be forward-declared as regular `const`, so you at least have the benefits of `constexpr` at your definition.

  • Whai

    Hello, I am confused.

    Aren't those codes the same ?
    Since we add the keyword extern, it would not duplicate those constants isn't ?
    (and I still don't understand why it's useless to add the keyword "extern" to constexpr)

    • nascardriver

      This won't work if placed in a header. If the header is included in multiple source files, every source file will define `pi`. However `pi` has external linkage and there are multiple definitions of `pi`, so you get a linker error.
      `inline constexpr` causes the variable to have external linkage too, but allows the variable to be defined multiple times.

  • srt1104

    In the "Global constants as external variables" example, does #including constants.h file in constants.cpp file serve any purpose or are we fine without #including it?

  • Abhishek

    if global constants are already external using extern specifier then why do we need header file can we simply have a cpp file and external linkage to constant variable.

  • ops

    Just to make it clear:

    constexpr functions and constexpr constructors are implicitly inline.

    constexpr constants are NOT implicitly inline.
    So, if I want to make them, I will have to be explicit:

    this is probably because of backward compatibility, right ?

  • Yousuf

    constants.h:

    Still preprocessor will copy all the contents of constants.h into all the files it is included but they--all the variables inside header files--won't be instantiated within all the included file.  Don't you think because of that source file gets bigger? I think still this is not better solution, probably module in C++20 will help us in this regard.

    • Richard

      Yeah bro, I have the same doubt with you. I think using "inline constexpr" still has the problem that when you change a const variable, all .cpp files including the header will need to be recompiled, which is not good for big projects. It cannot avoid duplication, either.

Leave a Comment

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