When do I use static
, inline
, extern
, const
, constexpr
etc. for global variables?
0. Overview
Global Variable Use Case |
Constants |
Non-Constants |
local to a single source file (i.e. declared and only used in a single file, not declared in a header) |
static const ,
static constexpr (C++11), or
const in anonymous namespace(C++11) |
static , or in anonymous namespace(C++11) |
declared, not defined in a header, defined in a source file |
extern const in header;
const in source, or
constexpr (C++11) in source |
extern in header; plain in source |
defined in a header, until C++17 |
imitate inline with templates and const or constexpr (C++11); or
enum for integers only |
imitate inline with templates |
defined in a header, since C++17 |
inline const , or
inline constexpr |
inline |
local to a single module(C++20) |
const or constexpr ; optionally inline |
optionally inline |
exported by a module(C++20) |
export const or
export inline constexpr |
export ; optionally inline |
In all cases above, constinit
(since C++20) may also be used, but not in combination with constexpr
. constinit const
has it uses too, and is not the same as constexpr
.
Note: the decision may also change based on whether the global variables are defined/used in dynamically linked libraries, and other factors.
1. Always use ensure that everything local to one source file has internal linkage
First of all, if the global variable is declared in, and only used in a single source file, then it must have internal linkage.
A global variable has internal linkage when:
- it is marked
static
- it is in an anonymous namespace(since C++11)
- it is
const
(or constexpr
(since C++11), since constexpr
implies const
)
If it doesn't have internal linkage, then you could easily run into an ODR violation. The example below is ill-formed, no diagnostic required.
// a.cpp
int counter = 0; // FIXME: surround with anonymous namespace, or add 'static'
// b.cpp
long counter = 0; // FIXME: surround with anonymous namespace, or add 'static'
Making the variable inline
(since C++17) does not solve this issue.
Internal linkage makes it safe, because the two counter
s would be distinct in each TU.
2. If something is declared, but not defined in a header, make it extern
Sometimes, it's not important for everyone to have a definition. For example:
// log.hpp
extern std::ofstream log_file;
// log.cpp
std::ofstream log_file = open_log_file();
It would be pointless to put the definition of log_file
to be in a header, and thus visible everywhere. Very little can be gained in terms of performance, and it would force us to make open_log_file()
visible everywhere too.
Note on extern constexpr
(since C++11) or extern const constinit
(since C++20)
Another valid but rare use case is extern constexpr
(since C++11) (see this answer), i.e. extern const
in a header, constexpr
(since C++11) in a source.
If available, this is better expressed through const constinit
(since C++20) in the source.
The purpose of this pattern is to avoid dynamic initialization for expensive-to-initialize look-up tables while keeping a header/source split.
3. If something is defined in a header, make it inline
(since C++17), or imitate inline
with templates(until C++17)
Sometimes, it is important to have a definition everywhere, such as for global constants that should be inlined:
inline constexpr float exponent = 1.25f;
Q: Do I really need inline
in combination with constexpr
?
Yes, you do. Consider the following example:
constexpr float exponent = 1.25f; // OK so far, but dangerous
// note: 'const float&' might seem contrived because you could work with 'float'.
// However, templates use const& everywhere, so it's very easy to
// run into this case indirectly.
inline const float& foo(const float& x) {
// IFNDR if the definition of foo appears in multiple translation units (TUs).
return std::max(x, exponent);
}
This program may be ill-formed, no diagnostic required, because exponent
has internal linkage (due to being const
) and is a distinct object in every TU.
Each definition of foo
may return a different reference to its own unique exponent
, which is a violation of [basic.def.odr] p14.5
Q: What can I do prior to C++17? inline
variables don't exist yet.
A similar mechanism has always existed in the form of templates.
// define a wrapper class template with a static data member
template <typename = void>
struct helper { static const float exponent; };
// define the static data member
template <typename T>
struct helper<T>::exponent = 1.25f;
// For convenience, make a reference with internal linkage to it.
// This is safe from ODR violations because
// [basic.def.odr] p14.5.2 has a special case for it, and
// this special case was retroactively applied to all C++ standards
// in the form of a defect report.
static constexpr float& exponent = helper<>::exponent;
// (static const float& prior to C++11)
Alternatively, specifically for scoped(since C++11) and unscoped enumerations, you can put the definition in a header without risk:
inline constexpr int array_size = 100; // since C++17
enum { array_size = 100; }; // pre-C++17 alternative
4. Make all global variables const
or even constexpr
(since C++11) whenever possible
In addition to the rules in 1., 2., 3., always make things const
when they can be.
This hugely simplifies ensuring correctness of your program, and enables additional compiler optimizations.
Q: What do I do if initialization is complicated?
Sometimes you can't just initialize a global with a simple expression, like here:
std::array<float, 100> lookup;
void init() {
for (std::size_t i = 0; i < lookup.size(); ++i) {
lookup[i] = compute(i);
}
}
However, don't late-initialize; instead use an immediately invoked lambda expression (IILE)(since C++11) or a regular function to perform initialization.
constexpr std::array<float, 100> lookup = [] {
decltype(lookup) result{};
/* ... */
return result;
}();
Q: What impact does const
or constexpr
(since C++11) have on linkage?
Making a global variable const
or constexpr
(since C++11) gives it internal linkage, but as explained in 3., this doesn't simplify anything for you.
You still have to worry about linkage and the ODR.
5. Static data members work differently
As seen in 3., static data members might not follow the same rules.
They have the same linkage as the class they belong to, with the following consequences:
- Static data members are quasi-
inline
in the case of class templates.
const
does not imply internal linkage for static data members.
Also, constexpr
for static data members implies inline
(since C++17).
static constexpr <type>{value};
– Phrixusinline
/static
constexpr
also doesn't cover nearly everything, since the answer is going to be different in C++98, C++11, C++17, and C++20 (with modules). – Riser