Defining global constants in C++1z?

假如想象 提交于 2019-12-05 02:49:27

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.

Back in the olden days we'd declare a constant in a header file and define it in a source file:

// constants.h
extern const int size;

// constants.cpp
#include "constants.h"
const int size = 3;

// usage
std::cout << size << '\n';

But maybe that's too simple; why not lard it up with a 10-line template and funky instantiation syntax?

I suggest to use a template wrapper, which allows to define a global constant of any type in header file and will spawn only a single copy through out all the translation units (no C++1z required):

template<typename TDummy = void> class
t_GlobalValueHolder final
{
     public: using
     t_Value = const int; // can be anything

     public: static t_Value s_value;
};

template<typename TDummy>
typename t_GlobalValueHolder<TDummy>::t_Value
t_GlobalValueHolder<TDummy>::s_value{42};

// usage
::std::cout << t_GlobalValueHolder<>::s_value;

Notice: typically access to s_value should be restricted to only a subset of classes / methods by either making value protected and deriving user class from t_GlobalValueHolder or just with listing all the relevant items as friends of t_GlobalValueHolder. And proper access control is another benefit of this method over "extern const " besides eliminating a need to have a translation unit containing value definition.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!