如果我们在类定义本身中定义一个成员函数,它是否必须被内联处理,或者它只是对编译器的一个可以忽略的请求。
7 回答
是的,在类体内定义的函数是隐式的inline
。
(与声明的其他函数inline
一样,这并不意味着编译器必须在调用函数的地方执行内联扩展,它只是启用“一个定义规则”的允许放宽,并结合定义必须是包含在使用该功能的所有翻译单元中。)
正如其他人所说,在类中定义的方法会自动内联请求。了解原因很有用。
假设不是。您必须为这样的函数生成代码,并且在任何地方调用它,跳转到子程序指令都必须通过链接器引用该位置。
class A {
public:
void f() { ... your code ... }
};
每次看到这段代码,如果它不是内联的,编译器只能假设它必须生成,所以它会生成一个符号。假设它是这样的:
A__f_v:
如果那个符号是全局的,那么如果你碰巧在不同的模块中多次包含这个类代码,你会在链接时遇到多重定义的符号错误。所以它不可能是全球性的。相反,它是本地文件。
想象一下,您将上述头文件包含在多个模块中。在每一个中,它将生成该代码的本地副本。这比根本不编译要好,但是当你真的只需要一个时,你会得到多个代码副本。
这导致了以下结论:如果您的编译器不打算内联一个函数,那么您最好在某处声明一次,而不是要求内联它。
不幸的是,内联和非内联是不可移植的。它由编译器编写者定义。一个好的经验法则是始终使每个内联,特别是所有本身只是调用函数的函数,内联,因为你消除了开销。任何低于三行线性代码的内容几乎都可以。但是如果代码中有一个循环,问题是编译器是否允许它内联,更重要的是,即使它做了你想要的,你会看到多少好处。
考虑这个内联代码:
inline int add(int a, int b) { return a + b; }
它不仅几乎与源代码中的原型一样小,而且内联代码生成的汇编语言也比对例程的调用要小。所以这段代码更小,更快。
而且,如果你碰巧传入了常量:
int c= add(5,4);
它在编译时解决,没有代码。
在 gcc 中,我最近注意到即使我不内联代码,如果它是文件本地的,他们无论如何都会偷偷地内联它。只有当我在单独的源模块中声明该函数时,它们才不会优化调用。
另一方面,假设您在 1000 行代码上请求内联。即使你的编译器很傻,可以配合它,你唯一节省的就是调用本身,代价是每次调用它时,编译器都必须粘贴所有代码。如果你调用该代码 n 次,您的代码会按例程 * n 的大小增长。因此,任何大于 10 行的内容都几乎不值得内联,除了只调用很少次的特殊情况。这方面的一个例子可能是只有两个人调用的私有方法。
如果您请求内联包含循环的方法,则只有在它经常执行少量次数时才有意义。但是考虑一个迭代一百万次的循环。即使代码是内联的,花费在调用中的时间百分比也很小。因此,如果您有带有循环的方法,无论如何它们往往会更大,那么这些方法值得从头文件中删除,因为它们 a) 往往会被编译器拒绝为内联,并且 b) 即使它们是内联的,通常也是不会提供任何好处
编译器必须将其视为内联请求——它可以忽略。有一些习惯用法用于在头文件中定义一些函数(例如空的虚拟析构函数)和一些必要的头文件定义(模板函数),但除此之外,请参阅GotW #33了解更多信息。
有些人注意到编译器甚至可能内联您从未要求过的函数,但我不确定这是否会破坏请求内联函数的目的。
它确实是内联的——但是编译器可以忽略任何内联请求。
这是对编译器的请求,它可以忽略。
2003 ISO C++ 标准说
7.1.2/2 带有 inline 说明符的函数声明(8.3.5、9.3、11.4)声明了一个内联函数。inline 说明符向实现表明,在调用点对函数体进行内联替换优于通常的函数调用
机制。
在调用点执行此内联替换不需要实现;然而,即使
省略了这个内联替换,7.1.2 中定义的内联函数的其他规则仍应得到遵守。7.1.2/3 在类定义中定义的函数是内联
函数。内联说明符不应出现在块作用域函数声明中。7.1.2/4 内联函数应在
使用它的每个翻译单元中定义,并且在每种情况下都应具有完全相同的定义
(3.2)。[注意:对内联函数的调用可能会
在其定义出现在翻译单元中之前遇到。] 如果具有外部链接的函数在一个翻译单元中被声明为内联,则它应在其出现的所有翻译单元中声明为内联;不需要诊断。具有外部链接的内联函数在所有翻译单元中应具有相同的地址。外部内联
函数中的静态局部变量始终引用同一个对象。外部内联函数中的字符串文字
是不同翻译中的同一对象
单位。
有两件事不应该混为一谈:
- 如何将函数标记为内联:在签名前使用内联定义它或在声明点定义它;
- 编译器将如何处理此类内联标记:无论您如何将函数标记为内联,它都将被编译器视为请求。