I see that many still tend to misunderstand the use of the inline
keyword, and I do understand them, as I was part of that team years ago.
In this article I’m focusing on free functions, but most of the remarks will be valid for member functions (including those that are implicitly inline
), template
d functions, and variables too (if you didn’t know, from C++17 you can inline
variables as well, with a very similar semantics to functions).
Also, when you see inline
please always consider that constexpr
implies inline
. So, you are currently reading two articles at the price of one! 100% more convenient!
Are you ready? Let’s go. Let’s start from the most important thing.
inline
is not for inlining code
Read it again: inline
is not for inlining code.
Really.
Code inlining happens more or less independently. I say “more or less” just because I didn’t check the compiler internals myself, but I’m told by am authoritative source (a compiler implementor) that it slightly increases the chances for a function to be inlined. Does the same hold for things that are implicitly assumed inline
(i.e. methods declared within a class)? I don’t know.
But, nonetheless, inline
is not for inlining.
Let me explain.
Problem
Consider this code:
myfunction.h
#include <iostream> void myfunction() { std::cout << "test" << std::endl; }
f1.h
void f1();
f1.cpp
#include "myfunction.h" void f1() { myfunction(); }
f2.h
void f2();
f2.cpp
#include "myfunction.h" void f2() { myfunction(); }
main.cpp
#include "f1.h" #include "f2.h" int main() { f1(); f2(); }
If we try to compile this, what we get is a linker error. Why? Because we have two implementations of the function myfunction
:
f2.cpp.o: in function `myfunction()': f2.cpp:(.text+0x0): multiple definition of `myfunction()'; f1.cpp.o:f1.cpp:(.text+0x0): first defined here
Now, we have more that one way to solve this.
Proposed solution 1: Move the function body to another translation unit
One would be to move myfunction()
definition to a different translation unit (i.e. a cpp
file), and leave the declaration in the include file myfunction.h
.
For this solution, we just need to change one file, and add another:
myfunction.h
void myfunction();
myfunction.cpp
#include "myfunction.h" #include <iostream> void myfunction() { std::cout << "test" << std::endl; }
Technically, you don’t even need to #include "myfunction.h"
in this case (but in real cases you probably want to do it anyway).
This solution is suboptimal, because the compiler wouldn’t be able to optimize the call (assuming no link-time optimization, LTO, or link-time code generation, LTCG).
Proposed solution 2: Make the function static
The second option is to inform the linker that the we know we have two function, and it shouldn’t bother, because we want to use the local function only.
This is done using the static
keyword (WARNING: this is suboptimal too, and, probably, not what you want… continue reading).
We will only change the include file in this case.
myfunction.h
#include <iostream> static void myfunction() { std::cout << "test" << std::endl; }
In the final executable we’ll have TWO functions, one in each translation unit, but those functions won’t be exported, so we are avoiding the error.
Proposed solution 3: Make the function inline
The last option is to specify that function inline
, this will do something slightly different. Again, we only change the header file.
myfunction.h
#include <iostream> inline void myfunction() { std::cout << "test" << std::endl; }
This will still leave the symbol public, but the linker will be instructed to only use one of the two function (more or less at random).
This is because we assume the two are, actually, the same function (this is often the correct assumption when the code is defined in a header file).
What’s the right solution?
That’s simple: if your intent is to have one function, either the first or the third are correct.
In addition, if you want to have potentially better code (without using LTO/LTCG), you really want to use inline
. In that case, the compiler has all the potential to optimize away your call (effectively inlining the call), but if it decides not to, no error will be generated.
How can I tell the difference?
“It’s all nice, but since there’s no difference, I’ll use static
when I don’t want the code to be inlined, and inline
when I want inlining to happen.” (off-the-cuff citation, but I heard this so many times…)
No, first of all you can tell the difference from within your code. If we change the code slightly, and we get and return the two function pointers they will differ in the second solution.
But you can tell the difference by exploring what your compiler produces. For example, if you’re on gcc/Linux, this is what you get from nm
by executing it on the two files f1.o
and f2.o
in the three cases (only including the relevant information).
f1.o | f2.o | |
Fail (original) | 00000000 T myfunction() | 00000000 T myfunction() |
New TU (1) | U myfunction() | U myfunction() |
static (2) | 00000000 t myfunction() | 00000000 t myfunction() |
inline (3) | 00000000 W myfunction() | 00000000 W myfunction() |
So what’s the meaning of that letter before myfunction()
?
T | The symbol is in the text (code), and is used globally. In our original code we have two functions defining the same global (strong) symbol twice, and this causes the fail. |
U | The symbol is undefined (that’s why there’s no offset), and it’s something expected to be defined somewhere else, which in the first solution is the new file myfunction.cpp .This symbol will be resolved at link time |
t | The symbol is in the text (code), and is used locally. In this case, both local symbols are used in their own translation unit, ant they do not conflict globally. |
W | The symbol is weak. Quoting directly from ` man nm `, here’s the definition of “weak”:“When a weak defined symbol is linked with a normal defined symbol, the normal defined symbol is used with no error. When a weak undefined symbol is linked and the symbol is not defined, the value of the symbol is determined in a system-specific manner without error.” What the man page doesn’t explain is what happen when weak symbols are used together. In that case, only one of the two is picked for everything, no error is generated, and the linking terminates successfully. |
So, the difference between the second and third case is the way the symbol duplication is handled.
What about static inline
That is not a thing.
The two things are conflicting, but the compiler won’t generate an error, and it will take static
in my experiments.
(update) I think it’s a deliberate decision to make static inline
behave like static
, because it’s the most conservative option (see Addendum).
Conclusions
Yes, I cheated. When I babbled about “better code” and “optimizing the code without LTO/LTCG” I really meant “the function will be inlined”, but I did it for a reason: thinking that inline
will inline your code is the wrong mental model.
The keyword inline
is really for creating weak symbols that gets unified at link time, and this will be even more important for variables. And I know, I should have avoided this last comment – I imagine I’ll have to write an entire new post about inline
variables now…
Addendum
Proofreading this, I realized there’s one little thing that I forgot to mention.
What happens if, accidentally, you define two different inline
functions with the same name and the same prototype and namespace, in two different translation units? As stated above, those will be two weak symbols, and only one will be picked. This can have unexpected results, so in this case the abuse of inline
can backfire (this use-case is indeed the correct specifier would static
, to differentiate the two symbols and enable only their local use).
#
Hello!
Good luck :)