Logo

Blog


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
static void Fun() { /* impl */ }
inline void Fun() { /* impl */ }
namespace { void Fun() { /* impl */ } }

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
static void Fun() { /* impl */ }

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
namespace { void Fun() { /* impl */ } }

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
namespace {
  void Tweet();
  void Tooth();
  void Post();
  void Send();
  void Publish();
}

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
namespace {
void Tweet();
void Tooth();
void Post();
void Send();
void Publish();
}

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:

KeywordImplementation fileHeader file
staticX (deprecated according to SF22)
inlineX
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