什么时候我应该写关键字内联的函数/方法在c++ ?

在看到一些答案后,一些相关的问题:

在c++中,什么时候我不应该为函数/方法写关键字“内联”? 什么时候编译器不知道什么时候使一个函数/方法'内联'? 当一个应用程序为一个函数/方法写“内联”时,它是否重要?


1)如今,几乎从来没有。如果内联一个函数是个好主意,编译器会在没有你帮助的情况下完成它。

2)。看到# 1。

(经过编辑,反映出你把问题分成了两个问题……)


实际上,几乎从来没有。你所做的只是建议编译器将给定的函数内联(例如,替换对该函数的所有调用/w它的函数体)。当然,这不能保证:编译器可能会忽略该指令。

编译器通常会很好地检测和优化这样的事情。


你要把它放在最开始,在返回类型之前。但是大多数编译器忽略了它。如果它是定义的,并且它的代码块较小,大多数编译器都认为它是内联的。


在开发和调试代码时,不要使用内联。这会使调试复杂化。

添加它们的主要原因是为了帮助优化生成的代码。通常,这是以增加的代码空间换取速度,但有时内联可以同时节省代码空间和执行时间。

在算法完成之前将这种思想扩展到性能优化是不成熟的优化。


在进行模板特化时,仍然需要显式内联函数(如果特化在.h文件中)


什么时候应该内联:

1.当人们想要避免调用函数时发生的开销,如参数传递,控制传递,控制返回等。

2.函数应该很小,经常被调用,并且内联是非常有利的,因为根据80-20规则,尽量使那些对程序性能有重大影响的函数内联。

正如我们所知,内联只是一个请求编译器类似于注册,它将花费你在对象代码大小。


天啊,我最讨厌的事之一。

内联更像静态或extern,而不是告诉编译器内联函数的指令。Extern, static, inline是链接指令,几乎只由链接器使用,而不是编译器。

据说,内联提示编译器,你认为函数应该内联。这在1998年可能是正确的,但十年后,编译器不需要这样的提示。更不用说,当涉及到优化代码时,人类通常是错误的,所以大多数编译器会忽略“提示”。

static - the variable/function name cannot be used in other translation units. Linker needs to make sure it doesn't accidentally use a statically defined variable/function from another translation unit. extern - use this variable/function name in this translation unit but don't complain if it isn't defined. The linker will sort it out and make sure all the code that tried to use some extern symbol has its address. inline - this function will be defined in multiple translation units, don't worry about it. The linker needs to make sure all translation units use a single instance of the variable/function.

注意:通常,将模板声明为内联是没有意义的,因为它们已经具有内联的链接语义。但是,模板的显式专门化和实例化需要内联使用。


具体问题解答:

When should I write the keyword 'inline' for a function/method in C++? Only when you want the function to be defined in a header. More exactly only when the function's definition can show up in multiple translation units. It's a good idea to define small (as in one liner) functions in the header file as it gives the compiler more information to work with while optimizing your code. It also increases compilation time. When should I not write the keyword 'inline' for a function/method in C++? Don't add inline just because you think your code will run faster if the compiler inlines it. When will the compiler not know when to make a function/method 'inline'? Generally, the compiler will be able to do this better than you. However, the compiler doesn't have the option to inline code if it doesn't have the function definition. In maximally optimized code usually all private methods are inlined whether you ask for it or not. As an aside to prevent inlining in GCC, use __attribute__(( noinline )), and in Visual Studio, use __declspec(noinline). Does it matter if an application is multithreaded when one writes 'inline' for a function/method? Multithreading doesn't affect inlining in any way.


编译时,GCC默认不内联任何函数 启用优化。我不知道visual studio - deft_code

我检查了Visual Studio 9(15.00.30729.01)通过/FAcs编译并查看汇编代码: 编译器产生了对成员函数的调用,而没有在调试模式下启用优化。即使函数被标记为__forceinline,也不会产生内联运行时代码。


什么时候编译器不知道什么时候使一个函数/方法'内联'?

这取决于所使用的编译器。不要盲目相信现在的编译器比人类更了解如何内联,也不要因为性能原因而使用它,因为它是链接指令而不是优化提示。虽然我同意这些观点在意识形态上是正确的,但遇到现实可能是另一回事。

在阅读了多个线程之后,出于好奇,我尝试了内联对我正在工作的代码的影响,结果是我得到了GCC的可测量加速,而英特尔编译器的速度没有提高。

(更多细节:数学模拟与少数关键函数定义类之外,GCC 4.6.3 (g++ -O3), ICC 13.1.0 (icpc -O3);将内联添加到临界点导致GCC代码加速6%)。

因此,如果你将GCC 4.6限定为现代编译器,那么如果你编写CPU密集型任务,并且知道瓶颈在哪里,内联指令仍然很重要。


在c++中,什么时候我不应该为函数/方法写关键字“内联”?

如果函数在头文件中声明,并在.cpp文件中定义,则不应编写关键字。

什么时候编译器不知道什么时候使一个函数/方法'内联'?

没有这种情况。编译器不能使函数内联。它所能做的就是内联对函数的部分或所有调用。如果它没有函数的代码,它就不能这样做(在这种情况下,链接器需要这样做,如果它能够这样做的话)。

当一个应用程序为一个函数/方法写“内联”时,它是否重要?

不,那完全不重要。


除非您正在编写一个库或有特殊的原因,否则您可以忘记内联,而是使用链接时间优化。它消除了函数定义必须在头文件中才能考虑跨编译单元进行内联的要求,这正是内联所允许的。

(但请参阅为什么不使用链接时间优化?)


我想用一个令人信服的例子来解释这篇文章中所有的伟大答案,以消除任何剩余的误解。

给定两个源文件,例如:

inline111.cpp: # include < iostream > 空白栏(); Inline fun() { 返回111; } Int main() { std:: cout < <“inline111:有趣 () = " << 有趣的 () << ", & 有趣= " < < (void *)与娱乐; 酒吧(); } inline222.cpp: # include < iostream > Inline fun() { 返回222; } 空格条(){ std:: cout < <“inline222:有趣 () = " << 有趣的 () << ", & 有趣= " < < (void *)与娱乐; }


Case A: Compile: g++ -std=c++11 inline111.cpp inline222.cpp Output: inline111: fun() = 111, &fun = 0x4029a0 inline222: fun() = 111, &fun = 0x4029a0 Discussion: Even thou you ought to have identical definitions of your inline functions, C++ compiler does not flag it if that is not the case (actually, due to separate compilation it has no ways to check it). It is your own duty to ensure this! Linker does not complain about One Definition Rule, as fun() is declared as inline. However, because inline111.cpp is the first translation unit (which actually calls fun()) processed by compiler, the compiler instantiates fun() upon its first call-encounter in inline111.cpp. If compiler decides not to expand fun() upon its call from anywhere else in your program (e.g. from inline222.cpp), the call to fun() will always be linked to its instance produced from inline111.cpp (the call to fun() inside inline222.cpp may also produce an instance in that translation unit, but it will remain unlinked). Indeed, that is evident from the identical &fun = 0x4029a0 print-outs. Finally, despite the inline suggestion to the compiler to actually expand the one-liner fun(), it ignores your suggestion completely, which is clear because fun() = 111 in both of the lines.


Case B: Compile (notice reverse order): g++ -std=c++11 inline222.cpp inline111.cpp Output: inline111: fun() = 222, &fun = 0x402980 inline222: fun() = 222, &fun = 0x402980 Discussion: This case asserts what have been discussed in Case A. Notice an important point, that if you comment out the actual call to fun() in inline222.cpp (e.g. comment out cout-statement in inline222.cpp completely) then, despite the compilation order of your translation units, fun() will be instantiated upon it's first call encounter in inline111.cpp, resulting in print-out for Case B as inline111: fun() = 111, &fun = 0x402980.


Case C: Compile (notice -O2): g++ -std=c++11 -O2 inline222.cpp inline111.cpp or g++ -std=c++11 -O2 inline111.cpp inline222.cpp Output: inline111: fun() = 111, &fun = 0x402900 inline222: fun() = 222, &fun = 0x402900 Discussion: As is described here, -O2 optimization encourages compiler to actually expand the functions that can be inlined (Notice also that -fno-inline is default without optimization options). As is evident from the outprint here, the fun() has actually been inline expanded (according to its definition in that particular translation unit), resulting in two different fun() print-outs. Despite this, there is still only one globally linked instance of fun() (as required by the standard), as is evident from identical &fun print-out.


c++内联与C内联完全不同。

#include <iostream>
extern inline int i[];
int i [5];
struct c {
  int function (){return 1;} // implicitly inline
  static inline int j = 3; // explicitly inline
  static int k; // without inline, a static member has to be defined out of line
  static int f (){return 1;} // but a static method does not // implicitly inline
};

extern inline int b;
int b=3;
int c::k = 3; // when a static member is defined out of line it cannot have a static
              // specifier and if it doesn't have an `inline` specifier in the
              // declaration or on the definition then it is not inline and always
              // emits a strong global symbol in the translation unit

int main() {
  c j;
  std::cout << i;
}

inline on its own affects the compiler, assembler and the linker. It is a directive to the compiler saying only emit a symbol for this function/data if it's used in the translation unit, and if it is, then like class methods, tell the assembler to store them in the section .section .text.c::function(),"axG",@progbits,c::function(),comdat or .section .bss.i,"awG",@nobits,i,comdat for unitialised data or .section .data.b,"awG",@progbits,b,comdat for initialised data. Template instantiations also go in their own comdat groups.

This follows .section name, "flags"MG, @type, entsize, GroupName[, linkage]. For instance, the section name is .text.c::function(). axG means the section is allocatable, executable and in a group i.e. a group name will be specified (and there is no M flag so no entsize will be specified); @progbits means the section contains data and isn't blank; c::function() is the group name and the group has comdat linkage meaning that in all object files, all sections encountered with this group name tagged with comdat will be removed from the final executable except for 1 i.e. the compiler makes sure that there is only one definition in the translation unit and then tells the assembler to put it in its own group in the object file (1 section in 1 group) and then the linker will make sure that if any object files have a group with the same name, then only include one in the final .exe. The difference between inline and not using inline is now visible to the assembler and as a result the linker, because it's not stored in the regular .data or .text etc by the assembler due to their directives. Only inline symbols with external linkage are given external comdat linkage like this -- static linkage (local) symbols do not need to go in comdat groups.

inline on a non-static method declaration in a class makes the method inline if it is defined out-of-line, this will prevent the method being emitted in the translation unit if it is not referenced in the translation unit. The same effect is achieved by putting inline on the out-of-line definition. When a method is defined out-of-line without an inline specifier and the declaration in the class is not inline then it will emit a symbol for the method in the translation unit at all times because it will have external linkage rather than external comdat linkage. If the method is defined in the class then it is implicitly inline, which gives it external comdat linkage rather than external linkage.

static inline on a member in a class (as opposed to method) makes it a static member (which does not refer to its linkage -- it has the linkage of its class which may be extern). static inline also allows static members of the class to be defined inside the class instead of needing to be declared in the class and then defined out-of-line (without static in the definition, which wasn't allowed without -fpermissive). *static inline* also makes the members inline and not static inline -- inline means that the definition is only emitted if it is referenced in the translation unit. Previously you had to specify inline on the out-of-line definition to make the member inline.

由于静态方法可以在类中定义,因此静态内联对类中定义的静态方法没有影响,类中定义的静态方法始终具有外部链接,是静态方法并且是内联的。如果它被定义在行外,那么inline必须被用来使它成为内联(即给予外部comdat链接而不仅仅是外部链接),static仍然不能被使用。

static inline at file scope only affects the compiler. It means to the compiler: only emit a symbol for this function/data if it's used in the translation unit and do so as a regular static symbol (store in.text /.data without .globl directive). To the assembler there is now no difference between static and static inline. Like the other forms of inline, it cannot be used on a class, which is a type, but can be used on an object of the type of that class. This form of static inline also cannot be used on members or methods of a function, where it will always be treated inline as the static means something else in a class (it means that the class is acting as a scope rather than it being a member of or method to be used on an object).

Extern inline是一个声明,意味着如果它被引用或抛出编译器错误,则必须在翻译单元中定义此符号;如果定义了它,那么将它视为常规的内联,对于汇编器和链接器,extern内联和内联之间没有区别,因此这只是一个编译器保护。

extern inline int i[];
extern int i[]; //allowed repetition of declaration with incomplete type, inherits inline property
extern int i[5]; //declaration now has complete type
extern int i[5]; //allowed redeclaration if it is the same complete type or has not yet been completed
extern int i[6]; //error, redeclaration with different complete type
int i[5]; //definition, must have complete type and same complete type as the declaration if there is a declaration with a complete type

如果没有错误行,上面的全部内容将折叠为inline int i[5]。显然,如果你做了extern inline int i[] = {5};然后,由于通过赋值显式定义,extern将被忽略。

I think the reason that static is not allowed on a static out-of-line definition without -fpermissive is because it implies that the static refers to static linkage, because it's not immediately obvious to the programmer that it is a member of a class or whether that class has , where the static means something different. -fpermissive ignores the static specifier on the out-of-line definition and it means nothing. In the case of a simple integer, k can't be defined out of a namespace, if c were a namespace, but if k were a function, then there would be no way of visibly telling from the line of code whether it is an out of line definition of a function in a namespace with static linkage, or an out-of-line definition of a static member with external linkage, and may give the wrong impression to the programmer / reader of the code.

对于局部类,在成员/方法上内联将导致编译器错误,并且成员和方法没有链接。

关于命名空间上的内联,请参见this和this


Inline关键字请求编译器用函数体替换函数调用,它首先计算表达式,然后传递。它减少了函数调用开销,因为不需要存储返回地址,函数参数也不需要堆栈内存。

使用时间:

提高绩效 减少呼叫开销。 因为它只是对编译器的请求,所以某些函数不会被内联 *大功能 有太多条件参数的函数 递归代码和带有循环的代码等等。


F.5:如果一个函数非常小并且对时间要求很高,那么就内联声明它

原因:一些优化器在没有程序员提示的情况下很擅长内联,但不要依赖它。测量!在过去40年左右的时间里,我们一直被承诺在没有人类提示的情况下,编译器可以比人类更好地内联。我们还在等待。指定inline(在类定义中编写成员函数时显式或隐式)可以鼓励编译器更好地完成工作。

来源:https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines.html Rf-inline

有关示例和异常,请访问源代码(参见上面)。


一个用例可能发生在继承上。例如,如果以下所有情况都为真:

你有某个类的基类 基类需要是抽象的 基类除了析构函数之外没有纯虚方法 您不希望为基类创建CPP文件,因为这是徒劳的

然后你必须定义析构函数;否则,你会有一些未定义的引用链接错误。此外,你不仅要定义,还要用inline关键字定义析构函数;否则,您将有多个定义链接错误。

这可能发生在一些只包含静态方法或编写基异常类的辅助类上。

让我们举个例子:

Base.h:

class Base {
public:
    Base(SomeElementType someElement) noexcept : _someElement(std::move(someElement)) {}

    virtual ~Base() = 0;

protected:
    SomeElementType _someElement;
}

inline Base::~Base() = default;

Derived1.h:

#include "Base.h"

class Derived1 : public Base {
public:
    Derived1(SomeElementType someElement) noexcept : Base(std::move(someElement)) {}

    void DoSomething1() const;
}

Derived1.cpp:

#include "Derived1.h"

void Derived1::DoSomething1() const {
    // use _someElement 
}

Derived2.h:

#include "Base.h"

class Derived2 : public Base {
public:
    Derived2(SomeElementType someElement) noexcept : Base(std::move(someElement)) {}

    void DoSomething2() const;
}

Derived2.cpp:

#include "Derived2.h"

void Derived2::DoSomething2() const {
    // use _someElement 
}

通常,抽象类有一些纯虚方法,而不是构造函数或析构函数。因此,你不必分离基类的虚析构函数的声明和定义,你可以只写virtual ~ base () = default;关于类声明。然而,在我们的案例中,情况并非如此。

据我所知,MSVC允许你在类声明上写这样的东西:virtual ~Base() = 0{}。所以你不需要用内联关键字分离声明和定义。但它将只与MSVC编译器工作。

现实世界的例子:

BaseException.h:

#pragma once

#include <string>

class BaseException : public std::exception {
public:
    BaseException(std::string message) noexcept : message(std::move(message)) {}
    virtual char const* what() const noexcept { return message.c_str(); }

    virtual ~BaseException() = 0;

private:
    std::string message;
};

inline BaseException::~BaseException() = default;

SomeException.h:

#pragma once

#include "BaseException.h"

class SomeException : public BaseException {
public:
    SomeException(std::string message) noexcept : BaseException(std::move(message)) {}
};

SomeOtherException.h:

#pragma once

#include "BaseException.h"

class SomeOtherException : public BaseException {
public:
    SomeOtherException(std::string message) noexcept : BaseException(std::move(message)) {}
};

main.cpp:

#include <SomeException.h>
#include <SomeOtherException.h>

#include <iostream>

using namespace std;

static int DoSomething(int argc) {
    try {
        switch (argc) {
        case 0:
            throw SomeException("some");
        case 1:
            throw SomeOtherException("some other");
        default:
            return 0;
        }
    }
    catch (const exception& ex) {
        cout << ex.what() << endl;
        return 1;
    }
}

int main(int argc, char**) {
    return DoSomething(argc);
}