inline?
看了一些答案后,还有一些相关的问题:
什么时候不应该在C++中的函数/方法中写关键字'inline'?
编译器什么时候不知道何时将函数/方法设为'inline'?
如果一个应用程序是多线程的,那么在一个函数/方法中写'inline'会有影响吗?
inline?
看了一些答案后,还有一些相关的问题:
什么时候不应该在C++中的函数/方法中写关键字'inline'?
编译器什么时候不知道何时将函数/方法设为'inline'?
如果一个应用程序是多线程的,那么在一个函数/方法中写'inline'会有影响吗?
哦,这是我非常讨厌的事情之一。
inline 更像是 static 或 extern 而不是指示编译器内联你的函数的指令。 extern, static, inline 是链接指令,几乎只被链接器使用,而不是编译器。
据说 inline 是暗示编译器你认为该函数应该被内联的。这在 1998 年可能是正确的,但十年后编译器不需要这样的提示。更不用说人类通常在优化代码方面是错误的,所以大多数编译器会直接忽略这个“提示”。
static - 变量/函数名不能在其他翻译单元中使用。链接器需要确保不会意外使用来自另一个翻译单元的静态定义变量/函数。
extern - 在这个翻译单元中使用这个变量/函数名,但不会抱怨它是否已被定义。链接器将解决这个问题,并确保所有尝试使用某些外部符号的代码都有其地址。
inline - 此函数将在多个翻译单元中定义,不用担心。链接器需要确保所有翻译单元使用变量/函数的单一实例。
注意:通常,声明模板为 inline 是无用的,因为它们已经具有 inline 的链接语义。但是,模板的显式特化和实例化需要使用 inline。
针对你的问题的具体答案:
C++ 中什么情况下应该为函数 / 方法编写关键字“inline”?
仅当您想在头文件中定义函数时才使用。更确切地说,仅当函数的定义可以出现在多个翻译单位中时才这样做。将小型(只有一行代码)函数定义在头文件中是一个好主意,因为它可以为编译器提供更多信息以优化代码。同时,这也会增加编译时间。
在C++中,什么情况下不应该在函数/方法中写入关键字“inline”?
不要添加内联,仅因为您认为编译器将其内联后代码运行速度更快。
在什么情况下编译器不知道何时使一个函数/方法‘内联’?
通常,编译器比您更擅长这样做。但是,如果没有函数定义,编译器无法选择将代码内联。在最大程度上优化的代码中,通常会内联所有私有方法,无论您是否要求。
另外,GCC中防止内联的方法是使用__attribute__(( noinline )),而在Visual Studio中使用__declspec(noinline)。
当一个应用程序是多线程的时候,对于函数/方法是否加'内联'关键字有影响吗?
多线程对内联没有任何影响。
inline111.cpp:
#include <iostream>
void bar();
inline int fun() {
return 111;
}
int main() {
std::cout << "inline111: fun() = " << fun() << ", &fun = " << (void*) &fun;
bar();
}
inline222.cpp:
#include <iostream>
inline int fun() {
return 222;
}
void bar() {
std::cout << "inline222: fun() = " << fun() << ", &fun = " << (void*) &fun;
}
情况A:
编译:
g++ -std=c++11 inline111.cpp inline222.cpp
输出:
inline111: fun() = 111, &fun = 0x4029a0
inline222: fun() = 111, &fun = 0x4029a0
讨论:
即使您应该具有相同的内联函数定义,但如果不是这种情况,C++编译器也不会标记它(实际上,由于分离编译,它没有办法检查)。这是您自己的责任!
链接器不会抱怨一个定义规则,因为fun()被声明为inline。但是,由于inline111.cpp是第一个被编译器处理的翻译单元(实际上调用fun()),所以编译器在其第一次遇到fun()时实例化它。如果编译器决定不从程序中的任何其他地方(例如从inline222.cpp)扩展fun()的调用,则对fun()的调用将始终链接到从inline111.cpp产生的实例(在该翻译单元中对fun()的调用也可能产生一个实例,但它将保持未链接)。确实,这可以从相同的&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。
案例 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 打印输出所表明的。.cpp文件都是自己的翻译单元。最好为启用/禁用-flto的情况添加案例。 - syockitinline 告诉链接器允许符号冲突(坚持使用第一个翻译单元的符号),但是为什么不需要测试这些符号是否相等呢?标准应该要求编译器为所有内联函数提供 LTO 信息并使这种检查成为强制性的! - Henrik Alsing Friberg如果模板特化在.h文件中,仍然需要显式内联函数。
1) 现在几乎没有必要这样做。如果将函数内联是个好主意,编译器会自己完成而不需要你的帮助。
2) 总是。见问题#1。
(编辑以反映您将您的问题分成两个问题...)
inline 关键字,例如在头文件中定义函数(这是将此类函数内联到多个编译单元中所必需的)。 - Melebiusinline说明符,则链接器会自动将其实例合并为一个,不使用ODR。 - Ruslan在C++中,什么时候不应该为函数/方法写 'inline' 关键字?
如果该函数在头文件中声明且在 .cpp 文件中定义,则不应该写关键字。
编译器何时不知道将函数/方法设置为 'inline'?
不存在这种情况。编译器无法使函数成为内联函数。它只能内联某些或全部调用该函数。如果编译器没有该函数的代码(在这种情况下,链接器需要在可能的情况下执行此操作),则不能这样做。
在一个多线程的应用程序中,如果在函数/方法中写了 'inline' 关键字,是否会有影响?
不会有任何影响。
inline关键字。但是,' deft_code '(967个赞和采纳的答案)提到相反的情况,只有在函数的定义可以出现在多个翻译单位时才应使用inline关键字。因此,我通过在头文件中声明带有inline关键字的函数并在.cpp文件中定义它来检查它,这会导致“未定义的引用”错误,所以你是正确的。现在你还提到……请参见下一个评论。 - Abhishek Manedeft_code 表示应该使用 inline 关键字,以便给编译器更多信息来优化代码。因此他的措辞在这里也是有意义的,但当我尝试像之前提到的那样在代码中使用时,会出现错误。所以我感觉你们两个的陈述相互矛盾,但都是有道理的,但当我实际检查时,你的陈述是正确的,所以你能否请解释一下这个问题。 - Abhishek Mane这取决于所使用的编译器。不要盲目相信现代编译器比人类更懂得如何进行内联,也不应出于性能原因而使用它,因为它是链接指令而不是优化提示。虽然我同意这些论点在理论上是正确的,但实际情况可能会有所不同。
在阅读了多个线程后,出于好奇,我尝试了一下内联对我正在处理的代码的影响,结果是我在GCC中获得了可测量的加速效果,而在Intel编译器中没有加速效果。
(更多细节:数学模拟与少数关键函数定义在类外部,GCC 4.6.3(g++-O3),ICC 13.1.0(icpc-O3);在关键点添加内联导致GCC代码+6%的加速)。
因此,如果您认为GCC 4.6是现代编译器,那么结果是,如果您编写CPU密集型任务并确切知道瓶颈在哪里,内联指令仍然很重要。
inline 在 C++ 中有语义上的差异(例如在处理多个定义的方式上),这在某些情况下非常重要(例如在模板中)。 - Pavel Minaev如果没有启用优化编译,gcc默认不会内联任何函数。我不知道Visual Studio的情况 - deft_code
我通过使用/FAcs编译并查看汇编代码来检查Visual Studio 9(15.00.30729.01)的情况:
即使在调试模式下,编译器也会生成对成员函数的调用,而没有启用优化。即使该函数标记为__forceinline,也不会生成内联运行时代码。
一个使用情况可能出现在继承中。例如,如果以下所有情况都为真:
那么你必须定义析构函数;否则,你将会遇到一些未定义的引用链接错误。此外,你不仅需要定义析构函数,还需要使用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 {}。所以您不需要使用 inline 关键字来分离声明和定义。但是这仅适用于 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);
}
F.5: 如果一个函数非常小且时间关键,声明为内联函数。
原因:一些优化器擅长在没有程序员提示的情况下进行内联,但不要依赖它。测量!在过去的40年左右的时间里,我们一直期望编译器可以在没有人类提示的情况下比人类更好地进行内联。我们仍在等待。明确指定内联(显式或隐式地在类定义中编写成员函数时)鼓励编译器做得更好。
来源:https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines.html#Rf-inline
有关示例和异常,请参见源代码(请参见上文)。
inline(见9.3/2)。 - Lightness Races in Orbitstatic来避免链接器错误,不是吗? - xyf