Why do class member functions defined outside the class (but in header file) have to be inlined?
Asked Answered
F

2

5

I have read existing answers on the two meanings of inline, but I am still confused.

Let's assume we have the following header file:

// myclass.h

#ifndef INCLUDED_MYCLASS
#define INCLUDED_MYCLASS

class MyClass
{
   public:
      void foo(); // declaration
};

inline void MyClass::foo()
{
    // definition
}

#endif

Why does void foo() which is defined outside the class in the file, have to be explicitly defined with inline?

Fad answered 2/6, 2018 at 18:31 Comment(13)
If foo were not part of the class, would you understand why you would have to mark its definition inline? Because the same reasoning applies here as well.Inclement
From my understanding if you define foo in the header and since that header can be included in more sources, you must tell the compiler to inform the linker about the definition duplicates, so that they can be merged. Is that correct?Fad
Inline functions don't exist as standalone entities...their source is compiled into the places they are called from..so the linker never even knows they are there. If they were not inlined, and the header was included in two different modules, the linker would see duplicate definitions and would give an error.Abaft
@Ray Toal...so even when I guard the header file i still have to inline since technically the header file would be include only once in that case?Fad
@RayToal I have seen the compiler generate function bodies for inline functions. inline is a request, not a guarantee, see: en.cppreference.com/w/cpp/language/inlineMagnetograph
@AndreasLukas Exactly :)Inclement
@Inclement oh okay. So even when I guard the header file I still have to inline the function definition since technically the header file would be include only once in that case?Fad
@AndreasLukas Yes, I am aware of that. My remark was addressed to Ray Toal.Magnetograph
@AndreasLukas header guards only protect multiple inclusions in a single file. They don't protect against the inclusion of the same header in two files that you then link together.Inclement
@Inclement Oh yes you right. Thanks a lot. This makes all sense now :)Fad
@PaulSanders Well there is a difference between the generic notion of an inline function (which by definition is one that has been compiled into its calling context) and the official definition from the C++ language. A C++ inline function is allowed to have multiple identical definitions across different translation units; whether the compiler makes it a "real" inline function or simply follows the language rules is up to the implementor.Abaft
@PaulSanders Oh jinx you linked to the same page :)Abaft
@RayToal Link Time Optimisation (LTO) has moved the goalposts with regard to inlining code in general - see discussion with Rakete below and also this answer, and links therein.Magnetograph
I
6

It's because you defined MyClass::foo in a header file. Or a bit more abstract, that definition will be present in multiple translation units (every .cpp file that includes the header).

Having more than one definition of a variable/function in a program is a violation of the one definition rule, which requires that there must be only one definition in a single program of every variable/function.

Note that header guards do not protect against this, as they only protect if you include the same header multiple times in the same file.

Marking the function definition as inline though means that the definition will always be the same across multiple translation units.1.

In practice, this means that the linker will just use the first definition of MyClass::foo and use that everywhere, while ignoring the rest,


1: If this is not the case your program is ill-formed with no diagnostics required whatsoever.

Inclement answered 2/6, 2018 at 18:52 Comment(0)
M
3

If you put MyClass::foo() in a header file and fail to declare it inline then the compiler will generate a function body for every compilation unit that #includes the header and these will clash at link time. The usual error thrown by the linker is something along the lines of Multiple definition of symbol MyClass::foo() or somesuch. Declaring the function inline avoids this, and the compiler and linker have to be in cahoots about it.

As you mention in your comment, the inline keyword also acts a hint to the compiler that you'd like the function to be actually inlined, because (presumably) you call it often and care more about speed than code size. The compiler is not required to honour this request though, so it might generate one or more function bodies (in different compilation units) which is why the linker has to know that they are actually all the same and that it only needs to keep one of them (any one will do). If it didn't know they were all the same then it wouldn't know what to do, which is why the 'classical' behaviour has always been to throw an error.

Given that the compilers these days often inline small functions anyway, most compilers also have some kind of noinline keyword but that is not part of the standard.

More about inline at cppreference.

Magnetograph answered 2/6, 2018 at 18:54 Comment(2)
I've always wondered whether the "inline acts as a hint" is actually the result of something else, namely that the definition of an inline function has to appear in the same TU. If the compiler can see the definition of a function, it has a higher probability of being inlined than a function where the compiler can't see the definition, but in recent times this is no longer the case due to things like LTO. Is this plausible?Inclement
@Inclement Most definitely. Before LTO, the compiler had to be able to see the code in every translation unit calling the function, hence the rule. But nowadays it's all very different. If you optimise for speed and turn LTO on, MSVC aggressively inlines things all over the place, regardless of how they are declared or where they are defined. I've not looked at gcc in any detail but it probably does much the same. That's why noinline (the exact keyword varies by compiler) can be useful - it let's you turn this off on a per-function basis and thus keep your code size down.Magnetograph

© 2022 - 2024 — McMap. All rights reserved.