C++ 'inline' is abhorrent, not because it does not guarantee to really do inlining, but because it does not even pretend to be doing inlining.
Topics
About: C++
The table of contents of this article
- Starting Context
- Target Context
- Orientation
- Main Body
- 1: Abhorrent 'inline': an Example in Which a Modification Is Ignored
- 2: What 'inline' Is Supposed to Do
- 3: 'inline' Probably Will Not Do What It Is Supposed to Do, but That Is Not the Problem
- 4: What 'inline' Probably Will Do: Creating Doppelgängers
- 5: Decisions of the Compiler Are Being Obtrusive to the Programmer, Which Is the Problem
- 6: Why and How Does the Above Example Exhibit Unreliable Behavior
- 7: Forcing GCC to Really Do Inlining
Starting Context
- The reader has a basic knowledge on C++, even if he or she doesn't accurately understand its some widely-misrepresented elements.
Target Context
- The reader will know what 'inline' may do and its consequences.
Orientation
There is an article that addresses what C++ 'template' really is and how to use it, including inlining.
There is an article that expounds on the intent and the usage of header files in C++.
Main Body
Stage DirectionHypothesizer 7 soliloquies.
1: Abhorrent 'inline': an Example in Which a Modification Is Ignored
Hypothesizer 7
I have a project which has an 'inline' function, like this.
theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassA.hpp
@C++ Source Code
#ifndef __theBiasPlanet_coreUtilitiesTests_inlineTest1_ClassA_hpp__
#define __theBiasPlanet_coreUtilitiesTests_inlineTest1_ClassA_hpp__
namespace theBiasPlanet {
namespace coreUtilitiesTests {
namespace inlineTest1 {
class ClassA {
private:
int i_memberVariableA0;
public:
inline ClassA ();
inline int inlineMethodA0 ();
};
}
}
}
#endif
theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassA.ipp
@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassA.hpp"
#include <iostream>
using namespace ::std;
namespace theBiasPlanet {
namespace coreUtilitiesTests {
namespace inlineTest1 {
inline ClassA::ClassA () {
}
inline int ClassA::inlineMethodA0 () {
cout << "### ClassA::inlineMethodA0 version: 1" << endl << flush;
return i_memberVariableA0;
}
}
}
}
Note that it is '.ipp', because the file is not any source file to be compiled but is a file to be included (but "i" in ".ipp" is for 'inline', not for 'included').
Let me create a new source file that calls the 'inline' function, but before that, I modify the implementation of the 'inline' function.
theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassA.ipp
@C++ Source Code
~
inline int ClassA::inlineMethodA0 () {
cout << "### ClassA::inlineMethodA0 version: 2" << endl << flush;
return i_memberVariableA0;
}
~
theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassC.hpp
@C++ Source Code
#ifndef __theBiasPlanet_coreUtilitiesTests_inlineTest1_ClassC_hpp__
#define __theBiasPlanet_coreUtilitiesTests_inlineTest1_ClassC_hpp__
namespace theBiasPlanet {
namespace coreUtilitiesTests {
namespace inlineTest1 {
class ClassC {
private:
int i_memberVariableC0;
public:
ClassC ();
int methodC0 ();
};
}
}
}
#endif
theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassC.cpp
@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassC.hpp"
#include <iostream>
#include "theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassA.ipp"
using namespace ::std;
namespace theBiasPlanet {
namespace coreUtilitiesTests {
namespace inlineTest1 {
ClassC::ClassC () {
}
int ClassC::methodC0 () {
ClassA l_classA;
return l_classA.inlineMethodA0 ();
}
}
}
}
I compile (only) the new source file, do linking to have a program, and execute the program, but the function call executes the old implementation, showing "ClassA::inlineMethodA0 version: 1".
2: What 'inline' Is Supposed to Do
Hypothesizer 7
'inline' is supposed to replace the function call with the body of the function, which I call 'really inlining'.
Any really 'inline' function is a blueprint, not any object, which means that it does not exist as any entity in any object file.
3: 'inline' Probably Will Not Do What It Is Supposed to Do, but That Is Not the Problem
Hypothesizer 7
It is not just that 'inline' does not guarantee to do what is supposed to do, but probably, it will not do what is supposed to do.
In fact, for all of my simple test cases, my GCC and Visual C++ never really have done inlining.
That itself is not particularly bad, if it is a matter that can be dismissed just as an optimization issue.
In fact, if the compiler judges that not really inlining is better performance-wise, I have no intention to complain about that.
But I insist that such optimization should be done unobtrusively to the programmer.
What does that mean? Well, the behavior of the program should not change just because the compiler made an optimization decision.
In other words, the function at least should outwardly look as though it is 'inline', even if it really is not.
That is what I require from any optimization.
Is that an excessive request? I do not think so.
4: What 'inline' Probably Will Do: Creating Doppelgängers
Hypothesizer 7
The calls of an 'inline' function in a source file probably will create a doppelgänger in the object file.
What does that mean? If the function is not really inlined, the calls have to remain function calls. . . . Calls to what? Of course, to a function object. But where is the function object? . . . As the function implementation is included in the source file of the function calls (via including the '.ipp' file), the compiler will create a function object in the object file of the source file. . . . But what if another source file calls the same 'inline' function? . . . The compiler will create another function object of the same function in the object file. . . . So, multiple function objects of the same function (doppelgängers) are scattered in multiple object files.
There are some people who think that 'inline' is just a hint that may be just ignored by the compiler, that is not so: even if function calls are not really inlined, 'inline' will have the effect that makes function objects doppelgängers (if such function objects did not become doppelgängers, a duplicate-object link error would occur).
5: Decisions of the Compiler Are Being Obtrusive to the Programmer, Which Is the Problem
The problem is that each of such doppelgängers does not become object-file-local.
Huh? Are such multiple doppelgängers visible globally? Yes, they are.
Is not a function call guaranteed to call the local doppelgänger? No, it is not.
Then, which doppelgänger is called for each call? . . . The one in the object file that is the first in the linking order among the competing object files.
The problem is that the decision by the compiler of whether the 'inline' function is really inlined obtrusively changes the behavior of the program: if the function is really inlined, the implementation of the compile time of the source file will be used, while if not, another implementation may be used.
My insistence is that if a function is declared to be 'inline', it has to at least behave like being 'inline', and the present behavior is not so.
6: Why and How Does the Above Example Exhibit Unreliable Behavior
Hypothesizer 7
Now I understand why the modification of the 'inline' function in the above example is ignored in the new function call.
1st, obviously, the function call is not really inlined.
2nd, the modification is certainly reflected in the new doppelgänger, but the doppelgänger is not used because there is a competing object file that is precedent in the linking order.
If the new object file is moved to be the 1st in the linking order among the competing object files, all the calls to the function will be to the new doppelgänger.
Or if an object file is eliminated from the project, all the calls to the function may unexpectedly change the behavior.
Or if the compiler secretly changes its mind and starts really inlining, the behavior will suddenly change.
7: Forcing GCC to Really Do Inlining
Hypothesizer 7
In fact, GCC can be forced to really do inlining, like this.
theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassA.hpp
@C++ Source Code
~
inline int inlineMethodA0 () __attribute__((always_inline));
~
As I have changed the above example code so, the program have changed the behavior to look as though the function is really inlined (in fact, it IS really inlined).