static vs. inline vs. namespace {

 c++   2017-06-24

In this post I’ll try to figure out whether I should use static, inline or unnamed namespaces for function definitions.

TL;DR

Here’s my attempt to build an algorithm to decide whether a class/function should be defined with either of the static/inline specifiers or put into an unnamed namespace. The first question I answer is: is the entity defined in a header file or in a .cpp file?

  • In a header — Is it a class or a function?
    • Class — There’s no need to do anything.
    • Function — Do you want it to behave differently for each translation unit (may be useful, for example, for logging)?
      • Yes — Use static.
      • No — Use inline.
  • In a .cpp file — Put it into an unnamed namespace.

static

It’s an old C-style method of defining functions in header files. This way, every translation unit gets its own copy of a function. What does that mean? The most obvious implication that pops into my head is that every local static variable defined inside that function gets an independent replica in every translation unit. For example, the program below would print

1
1

due to the fact that both main.cpp and proxy.cpp get their own versions of n from shared().

main.cpp
#include "proxy.hpp"
#include "shared.hpp"

#include <iostream>

int main() {
    std::cout << shared() << '\n';
    std::cout << proxy() << '\n';
    return 0;
}
proxy.cpp
#include "proxy.hpp"
#include "shared.hpp"

int proxy() {
    return shared();
}
proxy.hpp
#pragma once

int proxy();
shared.hpp
#pragma once

static int shared() {
    static int n = 0;
    return ++n;
}

In C, this is the only way to share function definitions between translation units (apart from the usual way of declaring a function in a header file and putting its definition to a .c file).

Properties

  • Using static, you can share function definitions between multiple translation units.
  • Each unit gets its own replica of the function: they have different addresses, their local static variables are independent, etc.
  • If different translation units define different functions with the same name using the static specifier, each unit can use its function without any issues. This might seem like an trivial claim, but other approaches sometimes disallow this, which is discussed below.

inline

It’s well-known that this keyword has pretty much nothing to do with whether a function will actually be inlined or not. It’s used much more often to define functions in header files, since every function defined this way will be the same (as in “will have the same address”) in every translation unit. Let’s try and adjust the definition of shared() accordingly:

shared.hpp
#pragma once

inline int shared() {
    static int n = 0;
    return ++n;
}

The same program would then print

1
2

since both main() and proxy() would call the same shared(), incrementing the same n.

Weird things happen when different translation units define different inline functions with the same name.

main.cpp
#include "another.hpp"

#include <iostream>

inline void shared() {
    std::cout << "main.cpp: shared()\n";
}

int main() {
    shared();
    another();
    return 0;
}
another.cpp
#include "another.hpp"

#include <iostream>

inline void shared() {
    std::cout << "another.cpp: shared()\n";
}

void another() {
    shared();
}
another.hpp
#pragma once

void another();

According to my simple experiments, this program produces different output based on which .cpp file was specified first on the command line during compilation. For example, this is the output of test.exe produced with either cl /W4 /EHsc main.cpp another.cpp /Fe:test.exe or g++ -Wall -Wextra -std=c++11 main.cpp another.cpp -o test.exe.

main.cpp: shared()
main.cpp: shared()

If we swap the order of .cpp files (another.cpp main.cpp instead of main.cpp another.cpp), the output becomes

another.cpp: shared()
another.cpp: shared()

No warnings/errors are emitted, making the situation truly disturbing. I tested this with GNU compiler version 5.4.0 and Microsoft compiler version 19.00.24210.

This behavior can be easily fixed either by making these functions static or by using unnamed namespaces (see below).

Properties

  • Using inline, you can share function definitions between multiple translation units.
  • Each translation unit will use the same function: it will have the same address in every translation unit, its local static variables will be shared, etc.
  • Defining different inline functions with the same name in different translation units is undefined behavior.

Two inline functions might be different even if they are the same textually. For example, they might reference two global variables which have the same name, but are defined in different translation units.

namespace {

With respect to function definitions, unnamed namespaces are, according to my understanding, quite similar to the static keyword. The additional value they provide is that they provide a way to apply static not only to functions, but to classes also. Remember the weirdness that happens when multiple translation units define different inline functions with the same name? Arguably, it gets even worse if we add classes to the equation.

main.cpp
#include "another.hpp"

#include <iostream>

struct Test {
    Test() {
        std::cout << "main.cpp: Test::Test()\n";
    }

    int x = 1;
};

int main() {
    Test test;
    std::cout << test.x << '\n';
    another();
    return 0;
}
another.cpp
#include "another.hpp"

#include <iostream>

struct Test {
    Test() {
        std::cout << "another.cpp: Test::Test()\n";
    }

    float y = 1.;
};

void another() {
    Test test;
}
another.hpp
#pragma once

void another();

Compiling this program the same way we did in the inline example (cl /W4 /EHsc main.cpp another.cpp /Fe:test.exe/g++ -Wall -Wextra -std=c++11 main.cpp another.cpp -o test.exe) yields different outputs depending on which .cpp file was specified first.

main.cpp: Test::Test()
1
main.cpp: Test::Test()
another.cpp: Test::Test()
1065353216
another.cpp: Test::Test()

I’m not sure why anybody would want that. This can be easily fixed by putting both Test classes into unnamed namespaces. The program than reads

main.cpp
#include "another.hpp"

#include <iostream>

namespace {

struct Test {
    Test() {
        std::cout << "main.cpp: Test::Test()\n";
    }

    int x = 1;
};

}

int main() {
    Test test;
    std::cout << test.x << '\n';
    another();
    return 0;
}
another.cpp
#include "another.hpp"

#include <iostream>

namespace {

struct Test {
    Test() {
        std::cout << "another.cpp: Test::Test()\n";
    }

    float y = 1.;
};

}

void another() {
    Test test;
}
another.hpp
#pragma once

void another();

After the adjustment, it produces the same output regardless of compilation options.

main.cpp: Test::Test()
1
another.cpp: Test::Test()

Notice how sharing classes defined in header files isn’t discussed here. The standard actually guarantees that if a class is defined in a header file, all translation units that use it share the definition.

Properties

  • Essentially, unnamed namespaces allow the static keyword to be applied to classes.
  • Similar to the static approach, each translation unit gets its own replica of a function/class, including their own local static variables, etc.
  • Defining different classes with the same name in different translation units (without utilizing unnamed namespaces) is undefined behavior.

Tricky cases

static + inline

In case a function is defined as static inline, static wins, and inline is ignored. The program below outputs

1
1
main.cpp
#include "proxy.hpp"
#include "shared.hpp"

#include <iostream>

int main() {
    std::cout << shared() << '\n';
    std::cout << proxy() << '\n';
    return 0;
}
proxy.cpp
#include "proxy.hpp"
#include "shared.hpp"

int proxy() {
    return shared();
}
proxy.hpp
#pragma once

int proxy();
shared.hpp
#pragma once

static inline int shared() {
    static int x = 0;
    return ++x;
}

In general, I can’t think of a reason to define a static inline function.

namespace { + inline

If an inline function is defined in an unnamed namespace, the unnamed namespace wins. The program below outputs

1
1
main.cpp
#include "proxy.hpp"
#include "shared.hpp"

#include <iostream>

int main() {
    std::cout << shared() << '\n';
    std::cout << proxy() << '\n';
    return 0;
}
proxy.cpp
#include "proxy.hpp"
#include "shared.hpp"

int proxy() {
    return shared();
}
proxy.hpp
#pragma once

int proxy();
shared.hpp
#pragma once

namespace {

inline int shared() {
    static int x = 0;
    return ++x;
}

}

In general, I can’t think of a reason to define an inline function in an unnamed namespace.

Separate method definitions

If you want to separate your class declaration from its method definitions while keeping them in the same header file, each method must be explicitly defined inline. The program below outputs

1
2
main.cpp
#include "another.hpp"
#include "shared.hpp"

int main() {
    Test test;
    another();
    return 0;
}
another.cpp
#include "another.hpp"
#include "shared.hpp"

void another() {
    Test test;
}
another.hpp
#pragma once

void another();
shared.hpp
#pragma once

#include <iostream>

struct Test {
    Test();
};

inline Test::Test() {
    static int x = 0;
    std::cout << ++x << '\n';
}