何时应该为 C++ 中的函数/方法编写关键字 inline
?
看到一些答案后,一些相关的问题:
什么时候不应该为 C++ 中的函数/方法编写关键字“内联”?
编译器何时不知道何时将函数/方法“内联”?
当为函数/方法编写“内联”时,应用程序是否是多线程的是否重要?
inline
(9.3/2)。
哦,伙计,我最讨厌的事情之一。
inline
更像是 static
或 extern
,而不是告诉编译器内联函数的指令。 extern
、static
、inline
是链接指令,几乎只由链接器使用,而不是编译器。
据说 inline
向编译器暗示您认为该函数应该被内联。这在 1998 年可能是正确的,但十年后编译器不需要这样的提示。更不用说在优化代码时人类通常是错误的,所以大多数编译器完全忽略了“提示”。
static - 变量/函数名称不能在其他翻译单元中使用。链接器需要确保它不会意外使用来自另一个翻译单元的静态定义的变量/函数。
extern - 在此翻译单元中使用此变量/函数名称,但如果未定义,请不要抱怨。链接器将对其进行排序,并确保所有尝试使用外部符号的代码都有其地址。
inline - 这个函数将在多个翻译单元中定义,不用担心。链接器需要确保所有翻译单元都使用变量/函数的单个实例。
注意: 通常,声明模板 inline
是没有意义的,因为它们已经具有 inline
的链接语义。但是,要使用模板 require inline
的显式特化和实例化。
您的问题的具体答案:
我什么时候应该为 C++ 中的函数/方法编写关键字“内联”?仅当您希望在标题中定义函数时。更准确地说,只有当函数的定义可以出现在多个翻译单元中时。在头文件中定义小(如在一个衬里)函数是一个好主意,因为它为编译器提供了更多信息以在优化代码时使用。它还增加了编译时间。
什么时候不应该为 C++ 中的函数/方法编写关键字“内联”?不要仅仅因为您认为如果编译器内联您的代码会运行得更快,就添加内联。
编译器何时不知道何时将函数/方法“内联”?一般来说,编译器会比你做得更好。但是,如果没有函数定义,编译器就没有内联代码的选项。在最大限度优化的代码中,无论您是否要求,通常所有私有方法都会被内联。为了防止在 GCC 中进行内联,请使用 __attribute__(( noinline )),而在 Visual Studio 中,请使用 __declspec(noinline)。
当为函数/方法编写“内联”时,应用程序是否是多线程的是否重要?多线程不会以任何方式影响内联。
我想通过一个令人信服的例子来为这个线程中的所有重要答案做出贡献,以消除任何剩余的误解。
给定两个源文件,例如:
inline111.cpp: #include
inline222.cpp: #include
案例 A:编译:g++ -std=c++11 inline111.cpp inline222.cpp 输出:inline111: fun() = 111, &fun = 0x4029a0 inline222: fun() = 111, &fun = 0x4029a0 讨论:即使你也应该这样做具有相同的内联函数定义,如果不是这种情况,C++ 编译器不会标记它(实际上,由于单独编译它无法检查它)。确保这一点是您自己的责任!链接器不会抱怨单一定义规则,因为 fun() 被声明为内联。但是,因为 inline111.cpp 是编译器处理的第一个翻译单元(实际上调用 fun()),所以编译器会在 inline111.cpp 中的第一次调用遇到时实例化 fun()。如果编译器决定不扩展 fun() 从程序中的任何其他位置调用它(例如从 inline222.cpp),对 fun() 的调用将始终链接到它从 inline111.cpp 生成的实例(对 fun() 的调用) 内 inline222.cpp 也可能在该翻译单元中生成一个实例,但它将保持未链接)。事实上,从相同的 &fun = 0x4029a0 打印输出中可以明显看出这一点。最后,尽管向编译器提出了实际扩展单行 fun() 的内联建议,但它完全忽略了您的建议,这很清楚,因为两行中的 fun() = 111。
即使您应该对内联函数有相同的定义,如果不是这种情况,C++ 编译器也不会标记它(实际上,由于单独编译,它无法检查它)。确保这一点是您自己的责任!
链接器不会抱怨单一定义规则,因为 fun() 被声明为内联。但是,因为 inline111.cpp 是编译器处理的第一个翻译单元(实际上调用 fun()),所以编译器会在 inline111.cpp 中的第一次调用遇到时实例化 fun()。如果编译器决定不扩展 fun() 从程序中的任何其他位置调用它(例如从 inline222.cpp),对 fun() 的调用将始终链接到它从 inline111.cpp 生成的实例(对 fun() 的调用) 内 inline222.cpp 也可能在该翻译单元中生成一个实例,但它将保持未链接)。事实上,从相同的 &fun = 0x4029a0 打印输出中可以明显看出这一点。
最后,尽管向编译器提出了实际扩展单行 fun() 的内联建议,但它完全忽略了您的建议,这很清楚,因为两行中的 fun() = 111。
案例B:编译(注意倒序):g++ -std=c++11 inline222.cpp inline111.cpp 输出:inline111: fun() = 222, &fun = 0x402980 inline222: fun() = 222, &fun = 0x402980 讨论:这个案例断言了案例 A 中讨论过的内容。请注意重要的一点,如果您注释掉 inline222.cpp 中对 fun() 的实际调用(例如,完全注释掉 inline222.cpp 中的 cout 语句),那么尽管编译翻译单元的顺序,fun() 将在 inline111.cpp 中第一次调用时被实例化,导致案例 B 的打印输出为 inline111:fun() = 111, &fun = 0x402980。
这个案例断言了案例 A 中讨论的内容。
请注意重要的一点,如果您注释掉 inline222.cpp 中对 fun() 的实际调用(例如,完全注释掉 inline222.cpp 中的 cout-statement),那么,尽管您的翻译单元的编译顺序不同,但 fun() 将是在 inline111.cpp 中第一次调用遇到时实例化,导致案例 B 的打印输出为 inline111:fun() = 111, &fun = 0x402980。
案例 C:编译(注意 -O2):g++ -std=c++11 -O2 inline222.cpp inline111.cpp 或 g++ -std=c++11 -O2 inline111.cpp inline222.cpp 输出:inline111: fun() = 111, &fun = 0x402900 inline222: fun() = 222, &fun = 0x402900 讨论:正如这里所描述的,-O2 优化鼓励编译器实际扩展可以内联的函数(还要注意 -fno-inline 是默认的,没有优化选项)。从这里的输出可以明显看出,fun() 实际上已经被内联扩展(根据它在特定翻译单元中的定义),导致两个不同的 fun() 输出。尽管如此,仍然只有一个全局链接的 fun() 实例(根据标准的要求),从相同的 &fun 打印输出中可以看出这一点。
正如这里所描述的,-O2 优化鼓励编译器实际扩展可以内联的函数(还要注意 -fno-inline 是默认的,没有优化选项)。从这里的输出可以明显看出,fun() 实际上已经被内联扩展(根据它在特定翻译单元中的定义),导致两个不同的 fun() 输出。尽管如此,仍然只有一个全局链接的 fun() 实例(根据标准的要求),从相同的 &fun 打印输出中可以看出这一点。
inline
函数成为未定义的行为。
.cpp
都是它自己的翻译单元。最好为启用/禁用的 -flto
添加案例。
inline
告诉链接器允许符号冲突(坚持来自第一个翻译单元的符号),但是为什么不需要测试符号的等价性呢?该标准应要求编译器为所有内联函数提供 LTO 信息,并强制进行此类检查!
在进行模板特化时,您仍然需要显式内联您的函数(如果特化在 .h 文件中)
1)如今,几乎从来没有。如果内联函数是个好主意,编译器会在没有你帮助的情况下完成它。
2) 总是。见#1。
(编辑以反映您将问题分为两个问题......)
inline
,例如在头文件中定义一个函数(这是在多个编译单元中内联此类函数所必需的)。
inline
说明符,则链接器会自动将其实例合并为一个,并且不使用 ODR。
什么时候不应该为 C++ 中的函数/方法编写关键字“内联”?
如果函数在标头中声明并在 .cpp
文件中定义,则您应该不编写关键字。
编译器何时不知道何时将函数/方法“内联”?
没有这种情况。编译器不能使函数内联。它所能做的就是内联对函数的部分或全部调用。如果它没有函数的代码,它就不能这样做(在这种情况下,如果它能够这样做,链接器需要这样做)。
当为函数/方法编写“内联”时,应用程序是否是多线程的是否重要?
不,这根本不重要。
inline
关键字。但 deft_code
(967 次投票和接受的答案)提及与此相反您应该只在函数的定义可以显示在多个翻译单元中时才使用 inline
关键字所以我通过在头文件中使用关键字 inline
声明函数并在 .cpp 中定义它来检查它文件,它会给出错误 undefined reference
。所以你是对的。现在你也提到了,........继续下一条评论
deft_code
表示 ,因此您应该使用 inline
关键字,以便为编译器提供更多信息。使用优化代码 所以他的措辞在这里也很有意义,但是当我尝试在前面提到的代码中使用它时会出错。所以我觉得你的两个陈述是相互对立的,但它们都是有道理的,但是当我实际检查你的陈述是真的时,请你对此有所了解。
编译器何时不知道何时将函数/方法“内联”?
这取决于使用的编译器。不要盲目相信现在的编译器比人类更了解如何内联,并且出于性能原因你永远不应该使用它,因为它是链接指令而不是优化提示。虽然我同意这些论点在意识形态上是否正确,但遇到现实可能是另一回事。
在阅读了多个线程之后,我出于好奇尝试了内联对我正在工作的代码的影响,结果是我对 GCC 获得了可测量的加速,而对英特尔编译器没有加速。
(更多细节:在类之外定义了少量关键函数的数学模拟,GCC 4.6.3 (g++ -O3),ICC 13.1.0 (icpc -O3);在关键点添加内联导致 GCC 代码加速 + 6%)。
因此,如果您将 GCC 4.6 限定为现代编译器,那么如果您编写 CPU 密集型任务并且知道瓶颈到底在哪里,那么 inline 指令仍然很重要。
实际上,几乎从来没有。您所做的只是建议编译器将给定的函数内联(例如,替换对该函数的所有调用/w 它的主体)。当然,不能保证:编译器可能会忽略该指令。
编译器通常会很好地检测+优化这样的事情。
inline
在 C++ 中具有 语义 差异(例如处理多个定义的方式),这在某些情况下很重要(例如模板)。
默认情况下,gcc 在未启用优化的情况下编译时不会内联任何函数。我不了解 Visual Studio – deft_code
我通过使用 /FAcs 编译并查看汇编代码检查了 Visual Studio 9 (15.00.30729.01) 的情况:编译器在调试模式下未启用优化的情况下产生对成员函数的调用。即使函数用 __forceinline 标记,也不会生成内联运行时代码。
F.5:如果函数非常小且时间紧迫,则将其声明为内联
原因:一些优化器擅长在没有程序员提示的情况下进行内联,但不要依赖它。措施!在过去 40 年左右的时间里,我们得到承诺,编译器可以在没有人类提示的情况下比人类更好地内联。我们还在等。指定内联(在类定义中编写成员函数时显式或隐式)鼓励编译器做得更好。
来源:https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines.html#Rf-inline
有关示例和例外情况,请转到来源(见上文)。
一个用例可能发生在继承上。例如,如果以下所有情况都为真:
你有某个类的基类
基类必须是抽象的
基类除了析构函数外没有纯虚方法
您不想为基类创建 cpp 文件,因为徒劳
然后你必须定义析构函数;否则,您将遇到一些 undefined referance
链接器错误。此外,您不仅要定义,还要使用 inline 关键字定义析构函数;否则,您将有 multiple definition
个链接器错误。
这可能发生在一些只包含静态方法或编写基础异常类等的辅助类上。
我们举个例子:
基数.h:
class Base {
public:
Base(SomeElementType someElement) noexcept : _someElement(std::move(someElement)) {}
virtual ~Base() = 0;
protected:
SomeElementType _someElement;
}
inline Base::~Base() = default;
派生1.h:
#include "Base.h"
class Derived1 : public Base {
public:
Derived1(SomeElementType someElement) noexcept : Base(std::move(someElement)) {}
void DoSomething1() const;
}
派生1.cpp:
#include "Derived1.h"
void Derived1::DoSomething1() const {
// use _someElement
}
派生2.h:
#include "Base.h"
class Derived2 : public Base {
public:
Derived2(SomeElementType someElement) noexcept : Base(std::move(someElement)) {}
void DoSomething2() const;
}
派生2.cpp:
#include "Derived2.h"
void Derived2::DoSomething2() const {
// use _someElement
}
通常,抽象类除了构造函数或析构函数之外还有一些纯虚方法。因此,您不必将基类的虚拟析构函数的声明和定义分开,您可以在类声明上写 virtual ~Base() = default;
。但是,在我们的案例中,情况并非如此。
据我所知,MSVC 允许您在类声明中编写类似的内容:virtual ~Base() = 0 {}
。因此,您不需要使用 inline 关键字将 decleration 和定义分开。但它只适用于 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)) {}
};
主.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);
}
除非您正在编写库或有特殊原因,否则您可以忘记 inline
并改用 链接时间优化。它消除了函数定义必须在标头中才能考虑跨编译单元内联的要求,这正是 inline
所允许的。
(但请参阅 Is there any reason why not to use link time optimization?)
C++ 内联与 C inline 完全不同。
#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
本身会影响编译器、汇编器和链接器。它是对编译器的一个指令,如果它在翻译单元中使用,则只为这个函数/数据发出一个符号,如果是,那么就像类方法一样,告诉汇编器将它们存储在 .section .text.c::function(),"axG",@progbits,c::function(),comdat
或 {3 部分} 用于未初始化数据或 .section .data.b,"awG",@progbits,b,comdat
用于初始化数据。模板实例化也在它们自己的 comdat 组中。
这在 .section name, "flags"MG, @type, entsize, GroupName[, linkage]
之后。例如,部分名称是 .text.c::function()
。 axG
表示该部分是可分配的、可执行的并且在一个组中,即将指定一个组名(并且没有 M 标志,因此不会指定 entsize); @progbits
表示该部分包含数据且不为空白; c::function()
是组名,并且该组具有 comdat
链接,这意味着在所有目标文件中,遇到带有该组名并带有 comdat 标记的所有部分都将从最终可执行文件中删除,除了 1 即编译器确保存在翻译单元中只有一个定义,然后告诉汇编器将其放在目标文件中自己的组中(1 组中的 1 节),然后链接器将确保如果任何目标文件具有同名的组,然后只在最终的 .exe 中包含一个。 inline
和不使用 inline
之间的区别现在对汇编器可见,因此链接器也可以看到,因为由于它们的指令,它没有被汇编器存储在常规的 .data
或 .text
等中。只有具有外部链接的内联符号才会被赋予这样的外部 comdat 链接——静态链接(本地)符号不需要进入 comdat 组。
inline
在类中的非静态方法声明上,如果方法被离线定义,则使方法内联,这将防止在翻译单元中未引用该方法时在翻译单元中发出该方法。将 inline
放在行外定义上可以达到相同的效果。当一个方法在没有 inline
说明符的情况下被离线定义并且类中的声明不是 inline
时,它将始终在翻译单元中为该方法发出一个符号,因为它将具有外部链接而不是比外部 comdat 链接。如果该方法在类中定义,那么它是隐式的 inline
,这给它提供了外部 comdat 链接而不是外部链接。
static inline
对类中的成员(与方法相反)使其成为 static
成员(不指代其链接——它具有其类的链接,可能是外部的)。 static inline
还允许在类中定义类的 static
成员,而不是需要在类中声明然后在外线定义(在定义中没有 static
,这是不允许的-fpermissive
)。 *static inline*
还使成员 inline
而不是 static inline
- inline
表示仅在翻译单元中引用定义时才发出定义。以前,您必须在外联定义上指定 inline
才能使成员 inline
。
由于类中可以定义static
方法,所以static inline
对类中定义的static
方法没有影响,它始终具有外部链接,是静态方法,是inline
。如果它的定义不符合规定,则必须使用 inline
使其成为 inline
(即提供给外部 comdat 链接而不仅仅是外部链接),并且 static
仍然不能使用。
文件范围内的 static inline
仅影响编译器。这对编译器意味着:只有在翻译单元中使用此函数/数据时才发出一个符号,并将其作为常规静态符号(存储在 .text /.data 中,而不使用 .globl 指令)。对于汇编程序来说,现在 static
和 static inline
之间没有区别。与 inline
的其他形式一样,它不能用于类型 class
,但可以用于该类类型的对象。这种形式的 static inline
也不能用于函数的成员或方法,它总是被视为 inline
,因为 static
表示类中的其他内容(这意味着该类充当范围而不是而不是作为对象的成员或方法使用)。
extern inline
是一个声明,意味着您必须在翻译单元中定义此符号,如果它被引用或抛出编译器错误;如果已定义,则将其视为常规 inline
,对于汇编器和链接器,extern inline
和 inline
之间没有区别,因此这只是编译器保护。
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
将被忽略,因为通过分配进行了显式定义。
我认为在没有 -fpermissive
的 static
外线定义中不允许 static
的原因是因为它暗示静态引用 static
链接,因为程序员并没有立即明白它是某个类的成员,或者该类是否具有 ,其中 static
表示不同的东西。 -fpermissive
忽略了外联定义中的 static
说明符,它没有任何意义。在一个简单整数的情况下,k
不能在命名空间之外定义,如果 c
是一个命名空间,但如果 k
是一个函数,那么就无法从行中明显看出的代码,无论是名称空间中具有 static
链接的函数的外线定义,还是具有外部链接的静态成员的外线定义,都可能给程序员/读者带来错误的印象编码。
对于本地类,成员/方法上的 inline
将导致编译器错误并且成员和方法没有链接。
对于命名空间上的 inline
,请参阅 this 和 this
inline 关键字要求编译器将函数调用替换为函数体,它首先计算表达式然后传递。它减少了函数调用开销,因为不需要存储返回地址,函数不需要堆栈内存论据。
何时使用:
提高性能 减少调用开销。由于这只是对编译器的请求,因此某些函数不会被内联 *大函数函数具有太多条件参数递归代码和带有循环的代码等。
inline
,常规编译中的内联 (-O0) 都不会内联函数。 C 内联:stackoverflow.com/a/62287072/7194773 C++ 内联:stackoverflow.com/a/62230963/7194773
你想把它放在最开始,在返回类型之前。但是大多数编译器都忽略了它。如果它被定义,并且它有一个更小的代码块,大多数编译器无论如何都会认为它是内联的。
开发和调试代码时,请忽略 inline
。它使调试复杂化。
添加它们的主要原因是帮助优化生成的代码。通常,这会以增加的代码空间换取速度,但有时 inline
会同时节省代码空间和执行时间。
在算法完成之前扩展这种关于性能优化的想法是premature optimization。
inline
函数通常不会内联,除非使用优化进行编译,因此它们不会以任何方式影响调试。请记住,这是一个提示,而不是一个要求。
inline
函数是内联的。在它们中设置有意义的断点是不可能的。
inline
不会改善现代编译器上的代码,它可以自行判断是否内联。
什么时候应该内联:
1.当想要避免调用函数时发生的事情的开销,如参数传递、控制转移、控制返回等。
2.函数要小,经常调用,内联是非常有利的,因为按照80-20规则,尽量将那些对程序性能有重大影响的函数内联。
正如我们所知,内联只是对编译器的请求,类似于寄存器,它会花费你的对象代码大小。
inline
已失去其作为优化提示的地位,并且大多数编译器仅使用它来允许多个定义 - 正如 IMO 应有的那样。更重要的是,自 C++11 以来,register
已完全被弃用,因为它先前的含义是“我比编译器更了解如何优化”:它现在只是一个没有当前含义的保留字。
inline
。
不定期副业成功案例分享