static, inline, or an unnamed namespace what's the difference
Today's post teaches the difference between a function declared
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
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:
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
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
inline: godbolt.org/z/rhvPe55Tj. Check out how many
StaticFun functions you can see in the assembly and how many
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:
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
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?
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).
The following table should guide you through decisions:
|Keyword||Implementation file||Header file|
|X (deprecated according to SF22)|
staticGenerate the function in every translation unit and don’t share it.
inlineeach 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.
[2023-03-17]: Thanks to various feedback, I improved this article. Huge thanks to Daniela Engert, Kohei Otsuka, and others.