14

What is the best way of declaring memory efficient Global Constants in C++17 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.

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 ?

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
Sahib Yar
  • 1,030
  • 11
  • 29
  • 11
    Just [don't use](https://stackoverflow.com/questions/484635) globals. – Henri Menke Aug 16 '17 at 10:07
  • I dont think there is a single best approach, but you always have to make a compromise. Using a global variable is already not "The best approach", but a compromise – 463035818_is_not_an_ai Aug 16 '17 at 10:16
  • 5
    @HenriMenke but there are some constants which are application wise, say value of PI, or some standard error message which needs to be called every where. – Sahib Yar Aug 16 '17 at 10:18
  • 7
    The standard library seems to prefer [`inline constexpr`](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0607r0.html). – Bo Persson Aug 16 '17 at 10:27
  • @SahibYar For pi there is the `M_PI` preprocessor macro defined in ``. – Henri Menke Aug 16 '17 at 10:28
  • 6
    @HenriMenke What you were referring to in your first comments is about global *variables*, this question is about global *constants*. – mkrieger1 Aug 16 '17 at 10:34
  • 6
    @HenriMenke [C++ Core Guidelines](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md) suggests that [_Global constants are useful._](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#note-16) – Sahib Yar Aug 16 '17 at 10:37
  • @Sahib Yar - almost any language feature can be useful - the question is whether it is useful enough to justify accepting all the associated trade-offs. If you look at other parts of the C++ Core Guidelines, they describe statics as useful only once, and encourage avoiding them several times. – Peter Aug 16 '17 at 11:40
  • exetern const if you actually require global constant variable. but in general usage of simple global constants is a de-optimization. compiler can inline your coonstant or move it to where it is used, if that's a non-literal. access to value stored in single place by CPU can be slower due limitation of memory cache – Swift - Friday Pie Aug 16 '17 at 13:07
  • Still waiting for proper answer. – Sahib Yar Aug 16 '17 at 15:54
  • @SahibYar Can you clarify what you mean by “best”? Fastest at execution time, easiest to code, fastest compile time, understandable to whoever’s reading it, least likely to cause bugs, …? I think most (but not all!) of these are available with [C++17’s inline variables](http://en.cppreference.com/w/cpp/language/inline), but some of those are more opinion-based than others and it’s hard to know which one you’re looking for. – Daniel H Aug 17 '17 at 22:54
  • @DanielH 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 ? – Sahib Yar Aug 17 '17 at 23:28
  • 1
    @SahibYar If you edit the question to use those criteria, it will probably be reopened. Both of the provided answers and `inline` variables will have the same memory usage; Pete’s answer would probably be the least compile time but not as much runtime optimization, VTT’s would provide the runtime optimization at the expense of compile time, and `inline` variables would be at least as fast as VTT’s answer, and faster to compile, but only work with C++17. Using `constexpr` when possible would give slower compiles, faster runtime, and a small chance of not taking any memory depending on usage. – Daniel H Aug 17 '17 at 23:36
  • @DanielH thanks for explanation, I have edited the question, I hope it will now be _reopened_ soon. – Sahib Yar Aug 17 '17 at 23:53
  • What do you mean by “Does there *mutability* will have any affect on there *constness* ?”? Something is `const` if and only if it is not mutable. There are some cases where this can be fudged around a bit, but global variables are usually not one of them, and most types are defined so that this doesn’t apply anyway. – Daniel H Aug 18 '17 at 05:05
  • @DanielH my point here, can mutability of `std::string` can affect any of the suggested technique, like `constexpr` or `const`. Mean that whatever will be suggested answer, please also consider the case of mutability of `std::string` – Sahib Yar Aug 18 '17 at 15:38
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/152273/discussion-between-sahib-yar-and-daniel-h). – Sahib Yar Aug 18 '17 at 16:19

3 Answers3

11

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.

Daniel H
  • 7,223
  • 2
  • 26
  • 41
6

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?

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • But Sir as you already know `const` makes _internal linkage_ which is against [ODR](https://en.wikipedia.org/wiki/One_Definition_Rule), I think. – Sahib Yar Aug 16 '17 at 11:37
  • 5
    @SahibYar -- `const` **by default** has internal linkage. Marking it `extern` gives it external linkage. There's no ODR issue with the code I wrote: `size` is defined in exactly one place. – Pete Becker Aug 16 '17 at 12:09
  • Now when I say `size * 5`, the compiler will insert a multiplication instruction instead of just an immediate 15. Brilliant! – Daniel H Aug 17 '17 at 22:09
  • @DanielH -- yes, that's because of the requirement in the question that the constant does not have internal linkage so that "a single copy is used throughout all translation units". – Pete Becker Aug 17 '17 at 22:14
  • You can make it use a single copy in all translation units but still be visible to the optimizer. You can use the other answer for a verbose way that works in older versions of C++, or the C++17 concept of [`inline` variables](http://en.cppreference.com/w/cpp/language/inline). Then you can just say `inline const int size = 3;` or `constexpr int size = 3;` in the header file, which will give it external linkage and make all translation units use the same copy. – Daniel H Aug 17 '17 at 22:39
  • @DanielH -- I look forward to seeing your answer. – Pete Becker Aug 17 '17 at 22:44
  • @PeteBecker I look forward to giving it, since with C++17 I think this is no longer primarily opinion-based and the question should be re-opened. In the meantime the best chance of having the original asker and other people interested in this question aware of this option is to comment about it. – Daniel H Aug 17 '17 at 22:50
  • 1
    @DanielH This question is now open so if you want to give your answer... – NathanOliver Aug 18 '17 at 11:39
1

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.

user7860670
  • 35,849
  • 4
  • 58
  • 84
  • static template member variable... woudn't we have a new instance for each case of instantiation? – Swift - Friday Pie Aug 16 '17 at 13:15
  • 1
    @Swift There will be only one instance. However if this was a regular (not template) class it wouldn't work like this. – user7860670 Aug 16 '17 at 13:20
  • Ah, i see, as long as TDummy is same. c++17 would allow to avoid external declaration in case of inline initialization? – Swift - Friday Pie Aug 16 '17 at 13:32
  • 1
    @Swift Well, `TDummy` can be locked to default value (that is to `void`) by placing `static_assert(::std::is_same::value, "");` inside of `t_GlobalValueHolder` definition. However typically no such check is required because it is unlikely that someone unintentionally instantiates `t_GlobalValueHolder` with different parameter. This approach should work even with standards older than C++11 (with some adjustments), it is just a special treatment existing for template static variables. – user7860670 Aug 16 '17 at 13:41
  • This is extremely verbose; a constant reference elsewhere could help with that. – Daniel H Aug 17 '17 at 22:14