static, inline, or an unnamed namespace what's the difference
Today's post teaches the difference between a function declared static
, inline
, or in an unnamed namespace. "What of these should I use when?" is a question that occasionally arises in my training classes.
Let me start by saying C++11 changed your options a little.
Here are the three options if we only look at free functions:
1 2 3 |
|
Please note that static
on member functions has a different meaning.
The first thing we have to talk about is where the declaration appears. The answer of what to use depends on whether the function is declared in a header or an implementation file.
The meaning of static
Let's start with the implementation file and static
first. With static
, you give the function internal linkage. Global symbols without static
have external linkage. In an implementation file:
1 |
|
implies that Fun
exists only in the scope of this translation unit. By that, Fun
is not accessible outside of the implementation file. We use this approach to hide functions, making them inaccessible outside the implementation. The function becomes a private part of the implementation, much like a private function inside a class.
But what if the symbol appears in multiple translation units because Fun
is declared in a header file? Well, the implication is nearly the same, Fun
exists only once in each translation unit. However, the difference is that due to the nature of include files being included by many implementation files, Fun
might exist more than once in your binary. A static
function in a header file can negatively impact your binary's footprint.
The meaning of inline
This brings me to inline
. The meaning of inline
is that each translation unit can provide a copy of the functions’ definition, given that all definitions are token equivalent. Should they be different, you end up with undefined behavior at run-time. The compiler either inlines the calls or merges the multiple definitions. From an outside view, inline
looks like suppressing the one definition rule (ODR) for a function.
Suppose the compiler goes for inlining. The compiler usually inlines the function if it thinks inlining is the cheapest option regarding the resulting binary size. If, on the other side, the compiler decides to keep the function and calls to it, the result is that you end up with exactly one implementation of Fun
, so no code duplications. This is what you want in your header files.
A Compiler Explorer example illustrates the scenario with both static
and inline
: godbolt.org/z/rhvPe55Tj. Check out how many StaticFun
functions you can see in the assembly and how many InlineFun
.
The meaning of an unnamed namespace
Now, what's left to talk about is the unnamed namespace option. What you've learned so far is true for C++ since 98. With C++11, we got unnamed namespaces like this one:
1 |
|
Various guidelines, like the Core C++ Guidelines in SF22 or the upcoming updated MISRA C++ guidelines, deprecate the use of static
, as we've discussed above. Instead, they recommend (sometimes it's more than a recommendation) using an anonymous namespace instead or an unnamed namespace as the standard calls them.
The reason is that unnamed namespaces provide the same property as static
. The name declared inside an unnamed namespace has internal linkage if not specified differently (see [basic.link]).
The difficulty with unnamed namespaces is the indentation. The LLVM Coding Standard, for example, states that the unnamed namespace feature is a great language feature, and then comes the but. The indentation namespaces usually cause, plus, should you have many symbols in the unnamed namespace, users have difficulty spotting that they are looking at a static
symbol.
Here is an example:
1 2 3 4 5 6 7 |
|
For only a few lines of code, spotting that all functions are in an unnamed namespace is probably easy. But what if we do not indent? Like here:
1 2 3 4 5 6 7 |
|
Now spotting that all functions are like static
functions is harder. Even for the first version with indention seeing things becomes harder in a large code base. Maybe we are looking at member functions Tweet
and so forth?
Interestingly static
was close to becoming deprecated in C++11. The keyword was already deprecated, but a late NB comment ensured that static
was kept (see CWG1012).
A Summary
The following table should guide you through decisions:
Keyword | Implementation file | Header file |
---|---|---|
static | X (deprecated according to SF22) | |
inline | X | |
namespace {} | X |
In short:
static
Generate the function in every translation unit and don’t share it.inline
each translation unit can provide its own copy of the functions’ definition. The compiler will then either inlines or merge multiple definitions if they are token equivalent.
Updates
[2023-03-17]: Thanks to various feedback, I improved this article. Huge thanks to Daniela Engert, Kohei Otsuka, and others.
Andreas