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.
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?
static
.inline
.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()
.
#include "proxy.hpp"
#include "shared.hpp"
#include <iostream>
int main() {
std::cout << shared() << '\n';
std::cout << proxy() << '\n';
return 0;
}
#include "proxy.hpp"
#include "shared.hpp"
int proxy() {
return shared();
}
#pragma once
int proxy();
#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).
static
, you can share function definitions between multiple
translation units.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:
#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.
#include "another.hpp"
#include <iostream>
inline void shared() {
std::cout << "main.cpp: shared()\n";
}
int main() {
shared();
another();
return 0;
}
#include "another.hpp"
#include <iostream>
inline void shared() {
std::cout << "another.cpp: shared()\n";
}
void another() {
shared();
}
#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).
inline
, you can share function definitions between multiple
translation units.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.
#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;
}
#include "another.hpp"
#include <iostream>
struct Test {
Test() {
std::cout << "another.cpp: Test::Test()\n";
}
float y = 1.;
};
void another() {
Test test;
}
#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
#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;
}
#include "another.hpp"
#include <iostream>
namespace {
struct Test {
Test() {
std::cout << "another.cpp: Test::Test()\n";
}
float y = 1.;
};
}
void another() {
Test test;
}
#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.
static
keyword to be applied to
classes.static
approach, each translation unit gets its own replica
of a function/class, including their own local static variables, etc.static
+ inline
In case a function is defined as static inline
, static
wins, and inline
is ignored.
The program below outputs
1
1
#include "proxy.hpp"
#include "shared.hpp"
#include <iostream>
int main() {
std::cout << shared() << '\n';
std::cout << proxy() << '\n';
return 0;
}
#include "proxy.hpp"
#include "shared.hpp"
int proxy() {
return shared();
}
#pragma once
int proxy();
#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
#include "proxy.hpp"
#include "shared.hpp"
#include <iostream>
int main() {
std::cout << shared() << '\n';
std::cout << proxy() << '\n';
return 0;
}
#include "proxy.hpp"
#include "shared.hpp"
int proxy() {
return shared();
}
#pragma once
int proxy();
#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.
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
#include "another.hpp"
#include "shared.hpp"
int main() {
Test test;
another();
return 0;
}
#include "another.hpp"
#include "shared.hpp"
void another() {
Test test;
}
#pragma once
void another();
#pragma once
#include <iostream>
struct Test {
Test();
};
inline Test::Test() {
static int x = 0;
std::cout << ++x << '\n';
}
My blog. Feel free to contribute or contact me.