内联
开门见山:内联(Inline)是符号的一种属性。内联的符号允许在链接过程中多次出现定义,而非内联的不允许。当一个符号是内联的时,其定义可以在多个翻译单元中分别给出。但标准要求此时每个翻译单元给出的内联定义应当是相同的,否则导致未定义行为。注意是要求定义完 · 全 · 一 · 致,但凡有一丁点不一样都不行。
将符号声明为内联的很简单,惟需用 inline
关键字修饰:
上面是内联函数的演示,这可以解决上一节中遗留下来的问题。内联变量也是没有问题的:
C++ 规定:用于修饰常量声明的 constexpr
蕴含 inline
。所以对于常量,请放心地使用它们,无需考虑重复定义的问题。
如果一个符号是类的成员,则问题变得更有趣一些。不过我们现在这里讲一下为什么我们需要内联。内联主要是为了解决一些有关头文件包含时导致的问题。
假设我有一个库 mylib
,里面定义了 f
这个函数。它长成这样:
然后 a.cpp
和 b.cpp
分别以头文件包含的形式使用了它:
注意,a.cpp
和 b.cpp
是两个独立的翻译单元。而它们都以预处理指令 #include
的方式引入了 void f();
的声明……而且是定义!所以,如果想要链接 a.cpp
和 b.cpp
,就会出现重复定义的问题。
传统的解决方案是不要这样设计库。总是把声明和定义分开来写:
然后在链接的时候额外提供 mylib.cpp
这个翻译单元。这样就保证了每个符号定义都只出现一次。但这种分离的写法会增加使用时的心智负担:我不仅要提供头文件给使用者,还要提供一个额外的翻译单元(源代码形式的 mylib.cpp
或者编译好的 mylib.o
)。这有时不太合适。为了应对这种情形,另外一种解决方案就是将 mylib.h
中的所有符号都声明为内联的。
此时,即便 a.cpp
b.cpp
都含有 f
的定义,但内联符号允许这样做;而且因为这两个定义是从同一个头文件中包含而来的,所以也能保证它们的定义一字不差。这就是内联符号的最大用处的体现。
然后再来看类成员的问题。规定:当类内定义成员函数时,这个成员函数总是内联的。
为什么这样定义?因为头文件中必须提供完整类的定义。(否则,使用者根本无法使用这个类。)
那么,a.cpp
和 b.cpp
在包含这个头文件时,非常自然而然地就各自包含了 A::f
的定义。如果 A::f
不是内联的,一个重定义的链接错误就会闪亮登场。所以,标准规定了成员函数的类内定义天生就是内联的。
如果类外定义成员函数则仍然是非内联的。你可以自己加上 inline
去修饰它,但一般不这样做。(当然,一旦分离了库的声明和定义,你就需要提供额外的翻译单元给用户。)
// mylib.h
class A {
void f();
};
// mylib.cpp
#include "mylib.h"
void A::f() {
// do something...
}
最后一样,类的静态成员变量的内联性。幸运的是,我们已经在第六章的一节提到了大量有关内联性的知识,只不过是从定义位置的角度出发的。我建议现在可以回过头看一看。现在,以内联的观点来看,则是总结为:
- 静态成员变量默认是非内联的;
- 类似地,可用
inline
将其修饰为内联的; - 类似地,
constexpr
蕴含了inline
。
当一个静态成员变量是非内联的,则需要在别的翻译单元写下其定义,才能保证不发生定义缺失或者重复定义等链接错误。
当一个静态成员变量是内联的,则类内定义是 OK 的。既然内联了,多次被包含也不会导致重定义问题。
但带上 const
限定之后,问题就多了起来,具体请查询第六章那一节结尾的表格。语法就那么规定的,我也没办法。
如果你以为内联的函数是指像宏展开一样运作的函数,建议阅读这一节结尾的注。内联函数确实会提示编译器那样做,但这不是强制要求。