Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Defining global constants in C++1z?

What is the best way of declaring memory efficient Global Constants in C++1z which do not make internal linkage, so a single copy is used though out all the translation units?

Although it's been mentioned in many places, we didn't have any singular "The best approach" question and answer, so here it is. Here is a partial list of places where I have found related questions.

  • constexpr global constants in a header file and odr
  • global declarations/initializations using static, const, constexpr
  • Global Constants in C++11
  • Defining global constant in C++
  • c++ global constants issue
  • Global Constants vs. Enumeration
  • Proper way to make a global “constant” in C++

I am looking for a solution for atleast all basic basic types, like int, double, char, std::string.

Edit 1: By best way, I mean "memory efficient". I know for internal linkage, multiple copies for each translation unit will be made, hence eating memory. Along with that if we can achieve fast execution time and compile time, that will be great, glamorizing (easiness of ) the code doesn't matter. Also I know external linkage will increase the fetching time because the processor may get cache misses at run time. But does latest C++ language support the best approach for all these ? Or may be suggested support for each case ?

Note: Also what will be best approach for std::string global constants ? Does there mutability will have any affect on there constness ?

like image 495
Sahib Yar Avatar asked Aug 16 '17 10:08

Sahib Yar


1 Answers

Summary

In C++17, the easiest way to define a global constant or a global variable is usually with an inline variable. You create one by simply putting a line like the following into your header file:

inline const std::string greeting = "Hello!";

If the global constant is of literal type, prefer using inline constexpr (the inline is optional if it’s a static class member) instead of inline const:

inline constexpr std::string_view greeting = "Hello!"sv;

This also works for variables, but many of the advantages no longer apply, so you might want to use another method:

inline unsigned int score = 0;

Details

First, the two main disadvantages to this method are:

  1. It doesn’t work before C++17, so if you need C++14 compatibility or earlier, you’ll need to do something else. The template trick VTT suggests is compatible with older standards, and should have similar semantics to inline variables, but it’s a hack which is no longer necessary if you only need C++17 support.
  2. It is somewhat slower to compile than extern variables, because the compiler needs to consolidate the multiple definitions, and because more information is available to the optimizer. The “somewhat” part turns into a “noticeably” if you might change the definition, but not the data type; since the value is now in a header file, this means recompiling everything that includes the header, instead of just re-linking. If you might need to change the data type everything will need to be recompiled no matter what.

If neither of those is important to you, I think this method beats the other ways of getting a global constant with external linkage and at most¹ one definition in the final executable².

An inline variable like this is mentioned in just one file, so it’s easy to change; this is particularly useful for header-only libraries. This also means it has its value available at compile time, so the optimizer can see it and maybe eliminate some usages.

Using constexpr

In C++17, static constexpr class members are automatically inline, so if your global constant should be part of a class’s scope, you can do something like

constexpr int favorite_number = -3;

Otherwise, you will need to say constexpr inline, which should still work. This will have the semantics described above, but also the usual advantages of constexpr, so the compiler will know that it can try to do more at compile time. For example:

#include <string_view>

using namespace std::literals;

inline constexpr std::string_view greeting = "Hello!"sv;

inline constexpr int scrabble_points[greeting.size()] = {4, 1, 1, 1, 1, 0};

int main() {
  int total = 0;
  for (int i : scrabble_points) {
    total += i;
  }
  return total;
}

is possible with constexpr, but not with just inline, because with constexpr it knows that greeting.size() is a compile-time constant and can be used as the size of an array.³ With optimizations, this could compile to a just a single mov instruction and ret, without including any copies of the string or array because it’s unnecessary.

With the new inline semantics, everything before main could have been in a header file included in multiple places, and there would still have been at most one copy.

Variables

The same method easily supports mutable variables by leaving off the const:

inline std::string player_name = "<subject name here>";

This is then a global variable with external linkage. Since it’s a variable, most of the advantages I’v mentioned over Pete’s answer are gone, but some (like only declaring the variable in one place and not needing to link any thing extra) are still present. They might not be worth the slight extra compile time and the lack of C++14 compatibility, though.


¹ For a const or constexpr variable, the compiler/optimizer might completely eliminate the variable if it isn’t needed. In theory, it might decide to copy it to an immediate value or something; in practice you probably shouldn’t worry about that because it would only do that if it had a good reason, and this should make the final executable smaller and/or faster. You could probably tune this with -Os instead of -O3.

² Each object file which used the constant would still have a copy, but those would be combined at link time. The only way to avoid that is with extern variables.

³ This simplified example works even without inline, or with the array being only const instead of constexpr, but those are useful for more complicated real-world situations.

like image 63
Daniel H Avatar answered Sep 17 '22 14:09

Daniel H