Use inline
only to satisfy the One Definition Rule. Don't bother with inline
for performance reasons.
If inline function is just to "provide a simple mechanism for the
compiler to apply more OPTIMIZATIONS."
If we're talking about the inline
keyword, then this is not what inline
is about. Like, at all.
All inline
does is ensure that a function doesn't violate the One Definition Rule. It might also provide a hint to the compiler that the compiler should blow the code out inline, which may or may not improve speed, but the compiler is entitled to ignore that hint. And in fact in modern compilers they will ignore this hint a great deal of the time.
One of the cases where the compiler is very likely to not blow the code out inline is with big functions. That doesn't mean the function shouldn't be declared inline
, however -- it all depends on how the function is declared, defined and used. But, again, it has nothing to do with performance.
Don't try to outsmart the compiler when it comes to applying optimization techniques. It's far better at making your program fast than you are.
Let's see what the Standard has to say on all of this.
7.1.2 Function specifiers
2/A function declaration (8.3.5, 9.3, 11.3) 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 defined by 7.1.2 shall
still be respected.
This tells us that inline
is a request to the compiler to blow the code out inline, and that compilers are not required to perform that substitution. But this also tells us that regardless of the compiler performing this inline substitution, the "other rules" defined in 7.1.2 still apply.
There was a time, long ago, when the optimization techniques employed by C++ compilers were primitive relative to the compilers of today. Back in those days, it might have made sense to use inline
as an optimization technique. But these days, compilers are much better at optimizing your code. The compiler applies techniques that, today, would make your code faster than inlining ever would, even if the function isn't actually inlined. (One possible example, RVO.)
So the end result of this is that while the original intent of 7.1.2/2 might have been to give the programmer a manual-optimization technique, modern compilers optimize so aggressively that this original intent is largely moot.
So all that's left for inline
are those "other rules." So what are those other rules? (C++11 verbiage)
4/An inline function shall be defined in every translation unit in
which it is odr-used and shall have exactly the same definition in
every case (3.2). [ Note: A call to the inline function may be
encountered before its definition appears in the translation unit. —
end note ] If the definition of a function appears in a translation
unit before its first declaration as inline, the program is
ill-formed. If a function with external linkage is declared inline in
one translation unit, it shall be declared inline in all translation
units in which it appears; no diagnostic is required. An inline
function with external linkage shall have the same address in all
translation units. A static local variable in an extern inline
function always refers to the same object. A string literal in the
body of an extern inline function is the same object in different
translation units. [ Note: A string literal appearing in a default
argument is not in the body of an inline function merely because the
expression is used in a function call from that inline function. — end
note ] A type defined within the body of an extern inline function is
the same type in every translation unit.
Let's take a look at an example. Suppose we have this class template
:
File: foo.h
#ifndef FOO_H
#define FOO_H
#include <string>
#include <sstream>
class StringBuilder
{
public:
template <typename T> inline StringBuilder& operator<<(const T& t)
{
mStream << t;
return * this;
}
operator std::string () const;
private:
std::stringstream mStream;
};
StringBuilder::operator std::string() const
{
return mStream.str();
}
#endif
If we #include
this file in main.cpp
and use the StringBuilder
, everything's fine:
File: main.cpp
#include <iostream>
#include <string>
int main()
{
double d = 3.14;
unsigned a = 42;
std::string s = StringBuilder()
<< "d=" << d << ", a=" << a;
std::cout << s << "\n";
}
Output:
jdibling@hurricane:~/dev/hacks$ ./hacks
d=3.14, a=42
But if we want to use the StringBuilder
in a second translation unit, we're going to have a problem:
File: other.cpp
#include <iostream>
#include <string>
#include "foo.h"
void DoSomethingElse()
{
unsigned long l = -12345;
long l2 = 223344;
std::string s = StringBuilder()
<< "l=" << l << ", l2=" << l2;
std::cout << s << "\n";
}
Compiler output:
ninja: Entering directory `.'
[1/3] Building CXX object CMakeFiles/hacks.dir/main.o
[2/3] Building CXX object CMakeFiles/hacks.dir/other.o
[3/3] Linking CXX executable hacks
FAILED: : && /usr/bin/g++ -Wall -std=c++11 -g CMakeFiles/hacks.dir/main.o CMakeFiles/hacks.dir/other.o -o hacks -rdynamic -lboost_regex-mt && :
CMakeFiles/hacks.dir/other.o: In function `std::operator|(std::_Ios_Openmode, std::_Ios_Openmode)':
/home/jdibling/dev/hacks/foo.h:21: multiple definition of `StringBuilder::operator std::string() const'
CMakeFiles/hacks.dir/main.o:/home/jdibling/dev/hacks/foo.h:21: first defined here
collect2: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.
StringBuilder::operator std::string()
is defined twice; once in main.cpp
and again in other.cpp
-- which violated the One Definition Rule.
We can fix this by making the function inline
:
class StringBuilder
{
public:
// [...]
inline operator std::string () const;
// ^^^^^^
private:
std::stringstream mStream;
};
Compiler output:
ninja: Entering directory `.'
[1/3] Building CXX object CMakeFiles/hacks.dir/main.o
[2/3] Building CXX object CMakeFiles/hacks.dir/other.o
[3/3] Linking CXX executable hacks
This works because now operator std::string
is defined on both translation units with exactly the same definition. It has the same effect as defining the function directly in the declaraton:
class StringBuilder
{
public:
operator std::string () const
{
return mStream.str();
}
private:
std::stringstream mStream;
};