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 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.9 -- Why global variables are evil
Index
6.7 -- External linkage

119 comments to 6.8 — Global constants and inline variables

  • Haldhar Patel

    Sorry to say but for me personally, I don't find this particular tutorial up to the mark like other previous tutorials . It is the first time I am really stuck here  trying to understand,  especially the " downsides of Global constant as external variable and how inline variable follows one definition rule and what are the differences between external and inline variable". I think I need to search for them in a different place.

    • Haldhar Patel

      This chapter makes sense now. After reading chapter 2, my all doubts are cleared. Previously I skipped chapter 2 as I thought I know it already, but I was wrong and every chapter of this tutorial is important. I am feeling bad for my previous comment by judging this tutorial without even reading the previous chapters.

      • Alex

        What was the key bit of missing information for you that made the above understandable? Maybe I can do a better job framing the problem and making sure the user knows what prerequisite knowledge this builds on...

        • Haldhar Patel

          After going through "multiple files", "header file" , and "constexpr" from the previous chapters I am finally able to understand the two paragraphs on downsides of global constants as external variable , my understandings are written inside "{..}"
            

          --> "However, there are a couple of downsides to this method. First, these constants  

          {e.g. extern const double pi{3.14159}; and others , which we can say by looking are compile time constants because they can be resolved during compilation of constants.cpp}

          are now considered compile-time constants only within the file they are actually defined in (constants.cpp), not anywhere else they are used.

          { for e.g. in main.cpp they only get declaration through header like extern const double pi; which is not resolved during compilation but during runtime through linker hence we can not have compile-time context{e.g. for array size} inside main(or any other file) and hence compiler will also not be able to optimize this away as they are not compile time constants anymore}

          -->Because the compiler compiles each source file individually, it can only see variable definition that appear in the active source file{main.cpp} or in one of the included header(constants.h).                            Variable definition in constants.cpp are not visible when the compiler compiles main.cpp

          {from the example provided header file only contains declarations so main.cpp will only have the declaration not the actual definition which is in constants.cpp}

          hence for this reason constexp variables cannot be separated into header {which contain only the declaration} and the source file {containing the definitions}, they have to be defined in the header file.

          --> I am a newbie in C++ and that's why the concepts takes time to load on my mind . I am sure other users might not have same problem understanding , your tutorials are great , it was a mistake from my side. You can delete my comments so that other users might not confuse with my own understandings):
          P.S. Thanks for your time.

  • Szymon :)

    "The linker will consolidate all inline definitions into a single variable definition."

    What does this mean? I'm confused because a single variable definition would look like this:

    And this is how two variable definitions look like:

    But how can the linker all definitions into just one? That doesn't make sense to me. We've got more than one variable, so how can the linker make put them all into only one definition?

    • Alex

      I updated the sentence to better indicate it consolidates all definitions for a variable into a single variable definition.

      Thus, in a.cpp you might have inline const int SquareSides { 4 }, and in b.cpp you might also have inline const int SquareSides { 4 };. The compiler will create a single and use a single definition for SquareSides.

  • Szymon :)

    Why in the example do we #include the file with forward declarations?

    constants.cpp:

    constants.h:

  • LightOfHeaven

    Hi, firstly, I apologize if I will be annoying, I plan to research below questions myself, but hearing from you would be of more importance to me, and this is my first time asking, which says that your way of teaching is nearly flawless, which is very impressive. That said, I have a couple of questions if you are willing to put some additional light on them:

    Between an 'external global' method (variables defined in a .cpp, declared in a header) and an 'inline global' method, I see that the 'best practice' advices to choose the latter, but here I will describe my newbish experience; prior to this interesting chapter, before learning all of this stuff, I have created a somewhat of a big program, for the purpose of storing and calculating various statistics and records for 5 of us players, playing a board game called 'Scrabble'. Initially, I went with an 'inline constexpr' method because it was presented in some earlier chapter and it looked good to me, very elegant and straightforward. Plenty of variables and functions, all under best practices (functions with a single purpose and passing everything as arguments, using pretty much all the knowledge gathered in first 5 chapters, even small, specific pieces).

    That said, later I came to this chapter, after finishing that optional one about bits (which was also delightful), and decided to transform all of my 'inline constexpr' variables into 'external const'. And behold! The Console was opening/starting significantly faster, while RAM usage was also slightly better, about 1.5MB less. I am pretty sure that I didn't imagine those differences. So here is my first question; isn't 'external const' a better way if we can use it (if we are not in need of 'constexpr' and can spare a source file for definitions?)

    Or maybe it is the other way, in a sense that 'constexpr' is generally more performant when it comes to 'run-time', due to variables being created and initialized at 'compile-time'? Does that make large and somewhat sophisticated programs to run/perform much better? Which brings me to another question; at several places, mostly in a comment section, it is said that 'const' is a 'compile-time' variable, how is that? I thought it's a 'run-time', while 'constexpr' is a 'compile time'. In some earlier chapter, 'const' is defined as a variable whose value is not known at 'compile-time'. Besides that, it is also mentioned that all 'constexpr' variables are also 'const', how can that be?

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

    • nascardriver

      `const` variables have to be initialized at their definition, they can be declared without a value.
      The compiler is happy with a declaration only. The linker will connect the declaration to the definition later.

      • Haldhar Patel

        Is this also applies to `constexpr`?
        I know that `constexpr` can not be declared as extern even after initialization and the reason for this I think is that they can't be declared because they are compile time constant   but `const` can as they behave as runtime constants anyway.
        If I am wrong , can you explain me why `constexpr` can not be declared after initialization as extern.
        Thanks in advance.

        • nascardriver

          `constexpr` can be declared as `extern` and it can be forward declared. What it can't do is be separated into header and source file. The compilers needs to see the definition of `constexpr` variables, because the compiler needs to evaluate them. That's not possible without a value. The compiler compiles each file individually, it can't see variables that are defined in other source files.

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

      • Corrado

        You should add this beautiful answer in the constant chapter, there is no mention in it.

        Without this answer I wouldn't figure out the why of this: 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.

  • 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???

    • nascardriver

      Without an error message it's hard to help you

      • I believe this is the same error:

        >(7,26): error C7525: inline variables require at least '/std:c++17'

  • 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]