内联

开门见山:内联(Inline)是符号的一种属性。内联的符号允许在链接过程中多次出现定义,而非内联的不允许。当一个符号是内联的时,其定义可以在多个翻译单元中分别给出。但标准要求此时每个翻译单元给出的内联定义应当是相同的,否则导致未定义行为。注意是要求定义完 · 全 · 一 · 致,但凡有一丁点不一样都不行。

将符号声明为内联的很简单,惟需用 inline 关键字修饰:

// a.cpp
inline void f() { /* [...] */ }

// b.cpp
inline void f() { /* [...] */ }

上面是内联函数的演示,这可以解决上一节中遗留下来的问题。内联变量也是没有问题的:

// a.cpp
inline int x{42};

// b.cpp
inline int x{42};

C++ 规定:用于修饰常量声明的 constexpr 蕴含 inline。所以对于常量,请放心地使用它们,无需考虑重复定义的问题。

如果一个符号是类的成员,则问题变得更有趣一些。不过我们现在这里讲一下为什么我们需要内联。内联主要是为了解决一些有关头文件包含时导致的问题。

假设我有一个库 mylib,里面定义了 f 这个函数。它长成这样:

// mylib.h
void f() {
    // do something...
}

然后 a.cppb.cpp 分别以头文件包含的形式使用了它:

// a.cpp
#include "mylib.h"
void a() {
    f();
}

// b.cpp
#include "mylib.h"
void b() {
    f();
}

注意,a.cppb.cpp 是两个独立的翻译单元。而它们都以预处理指令 #include 的方式引入了 void f(); 的声明……而且是定义!所以,如果想要链接 a.cppb.cpp,就会出现重复定义的问题。

传统的解决方案是不要这样设计库。总是把声明和定义分开来写:

// mylib.h
void f();

// mylib.cpp
void f() {
    // do something...
}

然后在链接的时候额外提供 mylib.cpp 这个翻译单元。这样就保证了每个符号定义都只出现一次。但这种分离的写法会增加使用时的心智负担:我不仅要提供头文件给使用者,还要提供一个额外的翻译单元(源代码形式的 mylib.cpp 或者编译好的 mylib.o)。这有时不太合适。为了应对这种情形,另外一种解决方案就是将 mylib.h 中的所有符号都声明为内联的。

// mylib.h
inline void f() {
    // do something...
}

此时,即便 a.cpp b.cpp 都含有 f 的定义,但内联符号允许这样做;而且因为这两个定义是从同一个头文件中包含而来的,所以也能保证它们的定义一字不差。这就是内联符号的最大用处的体现。

然后再来看类成员的问题。规定:当类内定义成员函数时,这个成员函数总是内联的

为什么这样定义?因为头文件中必须提供完整类的定义。(否则,使用者根本无法使用这个类。)

// mylib.h
class A {
    void f() {
        // 假设 A::f 在类内提供了完整的定义。
    }
};

那么,a.cppb.cpp 在包含这个头文件时,非常自然而然地就各自包含了 A::f 的定义。如果 A::f 不是内联的,一个重定义的链接错误就会闪亮登场。所以,标准规定了成员函数的类内定义天生就是内联的。

如果类外定义成员函数则仍然是非内联的。你可以自己加上 inline 去修饰它,但一般不这样做。(当然,一旦分离了库的声明和定义,你就需要提供额外的翻译单元给用户。)

// mylib.h
class A {
    void f();
};

// mylib.cpp
#include "mylib.h"
void A::f() {
    // do something...
}

最后一样,类的静态成员变量的内联性。幸运的是,我们已经在第六章的一节提到了大量有关内联性的知识,只不过是从定义位置的角度出发的。我建议现在可以回过头看一看。现在,以内联的观点来看,则是总结为:

  1. 静态成员变量默认是非内联的;
  2. 类似地,可用 inline 将其修饰为内联的;
  3. 类似地,constexpr 蕴含了 inline

当一个静态成员变量是非内联的,则需要在别的翻译单元写下其定义,才能保证不发生定义缺失或者重复定义等链接错误。

// mylib.h
class A {
    static int x;
};

// mylib.cpp
int A::x{42};

当一个静态成员变量是内联的,则类内定义是 OK 的。既然内联了,多次被包含也不会导致重定义问题。

class A {
    static inline int x{42}; // OK
};

但带上 const 限定之后,问题就多了起来,具体请查询第六章那一节结尾的表格。语法就那么规定的,我也没办法。

如果你以为内联的函数是指像宏展开一样运作的函数,建议阅读这一节结尾的注。内联函数确实会提示编译器那样做,但这不是强制要求。

最近更新:
代码未运行