What if I declare a big function as inline function?

c++, inline, performance

Solution

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;
};

Problem

I search some related questions (such as Benefits of inline functions in C++?), but I still have questions. If inline function is just to "provide a simple mechanism for the compiler to apply more OPTIMIZATIONS." - Then can I set every function as inline function? - If I wrongfully set a function as inline function, what would happen concerning performance? - Any threshold that tells me what size of function should not be inline function?

Original source

Related problems