C++ 探究 inline关键字的二重含义

由于inline关键字和“内联”概念之间的重要差别,本文对“inline”和“内联”做以下区分:

  • inlineinline关键字,inline函数指标记了inline的函数。
  • 内联 表示编译器对函数的内联操作,即在调用处按一定规则展开。内联函数 指执行了内联操作的函数。

0. inline的二重含义

inline的含义有以下2点

  • 允许函数定义出现在多个编译单元
  • 建议编译器内联此函数

接下来将对两个含义分别进行解释。与inline字面含义相悖的是,在现代c++中,第一个含义显然比第二个含义来得重要。

1. 允许函数定义出现在多个编译单元

c++标准原文如下:

Every program shall contain exactly one definition of every non-inline function or object that is used in that
program; no diagnostic required. The definition can appear explicitly in the program, it can be found in the
standard or a user-defined library, or (when appropriate) it is implicitly defined (see 12.1, 12.4 and 12.8).
An inline function shall be defined in every translation unit in which it is used.----ISO C++03 3.2 One Definition Rule

当一个函数定义被显式或隐式标记为inline时,该定义的相同拷贝可以多次出现在不同编译单元(.cpp文件)中。这个性质用来在头文件中定义函数,再通过#include拷贝到各个源文件中。如果要在头文件中定义函数,则函数必须标记为inline(显式或隐式)。否则只要#include该头文件超过一次,编译器就会报错(重复定义)。例如,可以在头文件中这样定义一个函数:

//head.h
#ifndef HEAD_H
#define HEAD_H
inline void func()
{
//函数定义......
}
#endif

在不同源文件中用inline对同一个函数声明做不一样的定义是非法的(尽管不一定被编译器发现,但原则上禁止此种行为)。

“原地”定义在类内的成员函数会被隐式地加上inline

例如:

//my_class.h
#ifndef HEAD_H
#define HEAD_H
class MyClass
{
public:
      void func()//等价于 inline void func()
      {
      //函数定义......
      }
}
#endif

标记inline的函数可能被内联,但不保证。一旦被内联,它们就像宏一般被展开在调用处,与周遭代码融为一体,再也没有一个独立的内联函数体来执行链接。
既然内联的本质决定其无法链接,编译器就必须在编译阶段在当前编译单元找到此内联函数的完整定义才有可能执行内联。不论有无inline,定义在不同编译单元的函数无法内联(它们之间的沟通需要链接,但之前说了,内联函数无法链接)。为了保证调用内联函数的位置处能找到其定义,头文件里声明的inline函数,在同一个头文件内就应当给出定义,而不是将声明和定义分别放在头文件和源文件中。
当然,如果程序员试图链接inline函数,或者取函数地址等仅限“真正”、“完整”的函数才能做到的操作,编译器也不会拒绝(因为本身inline就不保证内联,把他当一个普通函数有什么错?)。它会生成一个inline函数的非内联版来执行这些操作。总之,链接和内联不可兼得。

2. 建议内联

inline的第二个语义是建议编译器内联此函数。至于编译器在多大程度上重视此建议,则取决于编译器本身以及编译选项。事实上,现代编译器在开启优化后总是会自动内联值得内联的函数,不论它们是否被指定为inline,而被标记为inline的函数也不一定会被内联——如果编译器觉得没必要。关于程序员是应该亲自决定(或亲自“建议”)是否内联,还是应该将此交给编译器一直颇有争议。一部分人认为,程序员对代码有更高层次的理解,应当精细操控内联的过程,应该倾向于对每一处代码都考虑是否要用inline乃至编译器提供的强制内联功能;另一部分人认为现代编译器的优化水平已经很高,能够很好地衡量内联的收益和支出从而作出正确的内联决策,另外,程序员不当的强制内联会导致编译时间的延长以及代码的不必要膨胀,程序员应当倾向于不使用inline,或仅用它完成允许重复定义一节中所述的功能。

3. 总结

inline价值体现在它通过允许函数的重复定义,让程序员在头文件中也能定义函数,(例如在方法和模板使用隐式的inline)。内联方面,只有编译阶段能找到定义的代码才有可能被内联。因此对于既要共享又要提供内联可能性的函数,可以inline定义在头文件中。

posted @ 2020-05-12 18:30  BinarySong  阅读(228)  评论(0编辑  收藏  举报