According to what I read, the compiler is not obliged to substitute the function call of an inline function with its body, but will do so if it can. This got me thinking- why do we have the inline word if that is the case? Why not make all function inline functions by default and let the compiler figure out if it can substitute the calls with the function body or not?
inline is from C; it was not new to C++.
There are C keywords (
inline) that were designed to allow the programmer to assist in code optimization. These are generally ignored nowadays, since compilers can do better at register assignment and deciding when to inline functions (in fact, a compiler can either inline or not inline a function at different times). Code generation on modern processors is far more complicated than on the more deterministic ones common when Ritchie was inventing C.
What the word means now, in C++, is that it can have multiple identical definitions, and needs to be defined in every translation unit that uses it. (In other words, you need to make sure it can be inlined.) You can have an
inline function in a header with no problems, and member functions defined in a class definition are automatically effectively
inline was a very strong hint that calls to the function should be inlined.
But the only guaranteed effect of
inline is to allow a function to be defined (effectively identically) in multiple translation units, e.g., that you place the definition in a header file.
Nowadays, some compilers are very keen on following the inlining hint, e.g. g++. And some compilers take it less seriously, e.g. Visual C++. But all have to abide by the guarantee.
It is unfortunate that these two meanings -- optimization hint and what we might call a linker level discardable definition -- reside with the same keyword, because it means that you cannot practically have one without the other.
It is also unfortunate that
inline (or better, a separate keyword about discardable definition) ¹cannot be applied to data.
The need for linker level discardable data has increased as header-only modules have become more popular. E.g., many Boost sub-libraries are header-only.
For data you can, however, apply a little trick with templates. Define it in some class template, provide a
typedef with template parameter
void (or whatever). That's because the One Definition Rule makes a specific exception for templates.
inline variables will be supported in C++17.
Why not make all functions inline by default? Because it's an engineering trade off. There are at least two types of "optimization": speeding up the program and reducing the size (memory footprint) of the program. Inlining generally speeds things up. It gets rid of the function call overhead, avoiding pushing then pulling parameters from the stack. However, it also makes the memory footprint of the program bigger, because every function call must now be replaced with the full code of the function. To make things even more complicated, remember that the CPU stores frequently used chunks of memory in a cache on the CPU for ultra-rapid access. If you make the program's memory image big enough, your program won't be able to use the cache efficiently, and in the worst case inlining could actually slow your program down. To some extent the compiler can calculate what the trade offs are, and may be able to make better decisions than you can, just looking at the source code.
To understand “inline” you need to understand history and what life was like 20 (and 30) years ago.
We were writing code on computers that had little memory, so it was not possible for a compiler to process all the code that made up a program on one go. Compiler was also very slow, so you did not want to have to recompile code that had not changed – taking over 24hr (on a computer that cost as more than a top end car) to recompile all the code was normal for a few projects I worked on.
Therefore each code file was separately compiled into an object files. Each object file started with list of all function it contained, along with the “address” of the function. An object file also had a list of all functions it called in other object files along with the location of the call.
A linker would first read all the object files, and build up a list of all functions they exported, along with the file they were in and there address. It would then reread all the object files, outputting them to the programme file, while updating all “external” function calls with the address of the function.
The linker did not change or optimise the machine code produced by the compiler in any way other than to fix up references to external function calls. The linker was part of the operating system and predates most compilers. When people wrote a new compiler they needed it to work with current linkers, and to be able to link to current object files, otherwise system calls could not be made.
The compiler only ever saw the code in the “.c” or “.cpp” file it was compiling along with all included header files. So it could not make any optimisation based on code in other “.c” or “.cpp” files.
The “inline” keyword allowed the body of a function (method) to be defined in a header file, hence allowing the compiler to make use of the code of the function while compiling code that calls it. For example say you had a collection class defined in anther .cpp file, this class would have an “isEmpty” method, that contained one line of code, there would be a big speedup of the resulting program if instead of a call to a function, the function call was replaced with this one line.
The “inline” keyword was seen at the time as a “cheap and easy” way to allow encapsulation of data while avoiding the cost of function calls, without it a lot of programmers would have just access the private fields of the object. (Macros where a much worse way “inlining” code that where common at the time.)
These days “linkers” do a lot of code optimisation and tend to be written by the some team as the compiler. The compiler often just checks the code is correct and “compresses” it, leaving most of the task of machine code creation to the linker.
Let's see what the standard says (highlighted important parts in bold):
2. A function declaration with an inline specifier declares an inline function. The inline specifier indicates to the implementation that inline substitution of the function body at the point of call is to be preferred to the usual function call mechanism. An implementation is not required to perform this inline substitution at the point of call; however, even if this inline substitution is omitted, the other rules for inline functions shall still be respected.
— C++ standard, ISO/IEC 14882:2003, 7.1.2 Function Specifiers [dcl.fct.spec]
So, if you want to be sure, you should read the documentation of your compiler.
Inlining everything is a bad idea, as it might result in a lot of duplicated machine code...
So you'll have to know:
There are no simple answers: You have to play with it to see what is best. Do not settle for simplistic answers like, "Never use
inlinefunctions" or "Always use
inlinefunctions" or "Use
inlinefunctions if and only if the function is less than N lines of code." These one-size-fits-all rules may be easy to write down, but they will produce sub-optimal results.
— C++ FAQ, Inline Functions, 9.3 Do
inlinefunctions improve performance?
Let me give you a good reason for using the inline keyword.
On an embedded system, such as an ticket printer or similar smaller system. The processor is very limited and a function call (preparing function parameters on stack, call, fetch params from stack and put back answer etc..) can take several ms to execute, beside the function itself.
Lets say the call time is about 60ms (just for calling, not the actual function) and you have do 50 iterations (loop or iterative calls in a tree).
The time to just move forth and back from that function call will take 60 * 50 = 3000 (3 seconds).
If you have the memory you would definitely do an inline to save 3 seconds.
So inline are basically used when you need execution speed. In some projects I was involved with, the calling time was longer than the execution time, a classic situation when to use inline.