diff options
Diffstat (limited to '_posts')
27 files changed, 513 insertions, 0 deletions
diff --git a/_posts/2017-06-24-static-vs-inline-vs-unnamed-namespaces.md b/_posts/2017-06-24-static-vs-inline-vs-unnamed-namespaces.md new file mode 100644 index 0000000..e22ab21 --- /dev/null +++ b/_posts/2017-06-24-static-vs-inline-vs-unnamed-namespaces.md @@ -0,0 +1,277 @@ +--- +title: static vs. inline vs. namespace { +layout: post +excerpt: > + Should I use <code>static</code>, <code>inline</code> or unnamed namespaces + for function definitions? +custom_css: + - snippets.css +snippets_root_directory: snippets/static_vs_inline_vs_unnamed_namespaces/ +snippets: + static: + - static/main.cpp + - static/proxy.cpp + - static/proxy.hpp + - static/shared.hpp + inline: + - inline/shared.hpp + inline_weird: + - inline/weird/main.cpp + - inline/weird/another.cpp + - inline/weird/another.hpp + unnamed_namespaces_weird: + - unnamed_namespaces/weird/main.cpp + - unnamed_namespaces/weird/another.cpp + - unnamed_namespaces/weird/another.hpp + unnamed_namespaces_ok: + - unnamed_namespaces/ok/main.cpp + - unnamed_namespaces/ok/another.cpp + - unnamed_namespaces/ok/another.hpp + static_and_inline: + - static_and_inline/main.cpp + - static_and_inline/proxy.cpp + - static_and_inline/proxy.hpp + - static_and_inline/shared.hpp + unnamed_namespace_and_inline: + - unnamed_namespace_and_inline/main.cpp + - unnamed_namespace_and_inline/proxy.cpp + - unnamed_namespace_and_inline/proxy.hpp + - unnamed_namespace_and_inline/shared.hpp + separate_method_definitions: + - separate_method_definitions/main.cpp + - separate_method_definitions/another.cpp + - separate_method_definitions/another.hpp + - separate_method_definitions/shared.hpp +--- +In this post I'll try to figure out whether I should use `static`, `inline` or +unnamed namespaces for function definitions. + +`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 snippets/section.html section_id='static' %} + +In C, this is the only way to share function definitions between translation +units (apart from 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 there are issues with other +approaches that are 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: + +{% include snippets/section.html section_id='inline' %} + +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 snippets/section.html section_id='inline_weird' %} + +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 behaviour can be easily fixed by either 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 behaviour. + +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. +{: .alert .alert-info } + +`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 snippets/section.html section_id='unnamed_namespaces_weird' %} + +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 snippets/section.html section_id='unnamed_namespaces_ok' %} + +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 behaviour. + +Conclusion +---------- + +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. + +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 +``` + +{% include snippets/section.html section_id='static_and_inline' %} + +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 snippets/section.html section_id='unnamed_namespace_and_inline' %} + +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 +``` + +{% include snippets/section.html section_id='separate_method_definitions' %} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/shared.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/shared.hpp new file mode 100644 index 0000000..91313f2 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/shared.hpp @@ -0,0 +1,7 @@ +#pragma once + +inline int shared() +{ + static int n = 0; + return ++n; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/another.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/another.cpp new file mode 100644 index 0000000..668516a --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/another.cpp @@ -0,0 +1,13 @@ +#include "another.hpp" + +#include <iostream> + +inline void shared() +{ + std::cout << "another.cpp: shared()\n"; +} + +void another() +{ + shared(); +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/another.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/another.hpp new file mode 100644 index 0000000..9c26d3f --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/another.hpp @@ -0,0 +1,3 @@ +#pragma once + +void another(); diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/main.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/main.cpp new file mode 100644 index 0000000..4e32fb5 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/main.cpp @@ -0,0 +1,15 @@ +#include "another.hpp" + +#include <iostream> + +inline void shared() +{ + std::cout << "main.cpp: shared()\n"; +} + +int main() +{ + shared(); + another(); + return 0; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/another.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/another.cpp new file mode 100644 index 0000000..d8d53ae --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/another.cpp @@ -0,0 +1,7 @@ +#include "another.hpp" +#include "shared.hpp" + +void another() +{ + Test test; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/another.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/another.hpp new file mode 100644 index 0000000..9c26d3f --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/another.hpp @@ -0,0 +1,3 @@ +#pragma once + +void another(); diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/main.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/main.cpp new file mode 100644 index 0000000..8d2043f --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/main.cpp @@ -0,0 +1,9 @@ +#include "another.hpp" +#include "shared.hpp" + +int main() +{ + Test test; + another(); + return 0; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/shared.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/shared.hpp new file mode 100644 index 0000000..ef2d525 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/shared.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include <iostream> + +struct Test +{ + Test(); +}; + +inline Test::Test() +{ + static int x = 0; + std::cout << ++x << '\n'; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/main.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/main.cpp new file mode 100644 index 0000000..7fbeb56 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/main.cpp @@ -0,0 +1,11 @@ +#include "proxy.hpp" +#include "shared.hpp" + +#include <iostream> + +int main() +{ + std::cout << shared() << '\n'; + std::cout << proxy() << '\n'; + return 0; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/proxy.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/proxy.cpp new file mode 100644 index 0000000..a8eef26 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/proxy.cpp @@ -0,0 +1,7 @@ +#include "proxy.hpp" +#include "shared.hpp" + +int proxy() +{ + return shared(); +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/proxy.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/proxy.hpp new file mode 100644 index 0000000..7dfc52a --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/proxy.hpp @@ -0,0 +1,3 @@ +#pragma once + +int proxy(); diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/shared.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/shared.hpp new file mode 100644 index 0000000..6f812ab --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/shared.hpp @@ -0,0 +1,7 @@ +#pragma once + +static int shared() +{ + static int n = 0; + return ++n; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/main.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/main.cpp new file mode 100644 index 0000000..7fbeb56 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/main.cpp @@ -0,0 +1,11 @@ +#include "proxy.hpp" +#include "shared.hpp" + +#include <iostream> + +int main() +{ + std::cout << shared() << '\n'; + std::cout << proxy() << '\n'; + return 0; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/proxy.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/proxy.cpp new file mode 100644 index 0000000..a8eef26 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/proxy.cpp @@ -0,0 +1,7 @@ +#include "proxy.hpp" +#include "shared.hpp" + +int proxy() +{ + return shared(); +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/proxy.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/proxy.hpp new file mode 100644 index 0000000..7dfc52a --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/proxy.hpp @@ -0,0 +1,3 @@ +#pragma once + +int proxy(); diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/shared.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/shared.hpp new file mode 100644 index 0000000..a669247 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/shared.hpp @@ -0,0 +1,7 @@ +#pragma once + +static inline int shared() +{ + static int x = 0; + return ++x; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/main.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/main.cpp new file mode 100644 index 0000000..7fbeb56 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/main.cpp @@ -0,0 +1,11 @@ +#include "proxy.hpp" +#include "shared.hpp" + +#include <iostream> + +int main() +{ + std::cout << shared() << '\n'; + std::cout << proxy() << '\n'; + return 0; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/proxy.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/proxy.cpp new file mode 100644 index 0000000..a8eef26 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/proxy.cpp @@ -0,0 +1,7 @@ +#include "proxy.hpp" +#include "shared.hpp" + +int proxy() +{ + return shared(); +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/proxy.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/proxy.hpp new file mode 100644 index 0000000..7dfc52a --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/proxy.hpp @@ -0,0 +1,3 @@ +#pragma once + +int proxy(); diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/shared.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/shared.hpp new file mode 100644 index 0000000..c64cc41 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/shared.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace +{ + inline int shared() + { + static int x = 0; + return ++x; + } +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/another.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/another.cpp new file mode 100644 index 0000000..10128ef --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/another.cpp @@ -0,0 +1,18 @@ +#include "another.hpp" + +#include <iostream> + +namespace +{ + struct Test + { + Test() { std::cout << "another.cpp: Test::Test()\n"; } + + float y = 1.; + }; +} + +void another() +{ + Test test; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/another.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/another.hpp new file mode 100644 index 0000000..9c26d3f --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/another.hpp @@ -0,0 +1,3 @@ +#pragma once + +void another(); diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/main.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/main.cpp new file mode 100644 index 0000000..c0c0664 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/main.cpp @@ -0,0 +1,21 @@ +#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; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/another.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/another.cpp new file mode 100644 index 0000000..8a125ec --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/another.cpp @@ -0,0 +1,15 @@ +#include "another.hpp" + +#include <iostream> + +struct Test +{ + Test() { std::cout << "another.cpp: Test::Test()\n"; } + + float y = 1.; +}; + +void another() +{ + Test test; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/another.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/another.hpp new file mode 100644 index 0000000..9c26d3f --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/another.hpp @@ -0,0 +1,3 @@ +#pragma once + +void another(); diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/main.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/main.cpp new file mode 100644 index 0000000..8ab8fb8 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/main.cpp @@ -0,0 +1,18 @@ +#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; +} |