内联与ODR¶
Abstract
| 场景 | 是否使用 inline |
|---|---|
| 头文件中的自由函数定义 | 需要 |
| 头文件中的全局/命名空间变量定义(C++17+) | 需要 |
| 类内定义的成员函数 | 隐式具备,一般不必再写 |
| 类外但在头文件中的成员函数定义 | 需要显式 inline |
仅存在于单个 .cc 中的定义 | 不需要 |
| 指望「一定更快」 | 不要只依赖 inline |
inline 在 C++ 里主要有两层含义:
-
链接(linkage):允许多个翻译单元包含同一份定义而不违反 ODR。
-
优化(常被误解):口语里说的「内联展开」——现代编译器不保证因写了
inline就一定展开;是否内联由优化级别等决定。
教学与工程上应把 inline 首先当作 ODR/头文件机制,而不是「加速开关」。
ODR:单一定义规则¶
ODR(One Definition Rule,单一定义规则)要求:整个程序中,每个非 inline 的变量、函数、类等最多只能有一份定义。
头文件会被多个 .cpp / .cc 反复 #include,每个翻译单元都会复制头里的内容。若在头里写:
// bad.h
int Factorial(int n) {
return n <= 1 ? 1 : n * Factorial(n - 1);
}
每个包含 bad.h 的源文件都会生成一份 Factorial 定义 → 链接阶段报 multiple definition。
解决办法(二选一):
-
声明放头文件,定义只放在一个
.cc里; -
或定义放在头文件里,并标记为
inline(以及 C++17 的inline变量等,见下文)。
inline 函数¶
头文件中的函数体¶
// good.h
inline int Factorial(int n) {
return n <= 1 ? 1 : n * Factorial(n - 1);
}
多个翻译单元可以各含一份相同的 inline 定义,链接器合并为一份实体。
| 位置 | 建议 |
|---|---|
.h / .hh 里的函数实现 | 使用 inline(或类内成员定义,见下) |
仅在一个 .cc 中的实现 | 一般不要 inline;头文件只保留声明 |
类内成员函数:隐式 inline¶
在类(或 struct)内部定义的成员函数,自动具有 inline 性质:
struct Foo {
int Get() const { return x_; } // 隐式 inline
private:
int x_{};
};
若在类外写定义且定义仍在头文件中,需显式 inline:
struct Foo {
int Get() const;
};
inline int Foo::Get() const { return x_; }
与 运算符重载 中在类内定义的 friend operator== 同理:类内定义 → 隐式 inline,通常不必再写 inline。
inline 变量(C++17)¶
C++17 之前,命名空间作用域的变量若在头文件中定义,也容易触发 ODR 冲突。
C++17 起可对变量使用 inline:
inline constexpr int kMaxRetries = 3;
这样多个源文件 include 同一头文件时,仍只有一份变量实体。
没有 inline 时(头文件中):
const int kVersion = 1; // 每个 .cc 可能各有一份 → 链接 duplicate symbol
inline 与 constexpr¶
二者常一起出现,职责不同:
| 关键字 | 主要作用 |
|---|---|
constexpr | 在满足条件时于编译期求值;可用于常量表达式上下文 |
inline | 允许多个翻译单元共享同一定义(ODR) |
inline constexpr size_t kBufferSize = 4096;
-
constexpr:表达「可作为编译期常量」。 -
inline:表达「可以安全地写在头文件里被多处 include」。
Tip
类内的 static constexpr 整型成员有时仅在类内初始化即可;类类型的常量对象(如自定义 struct)在头文件中定义时,C++17 起通常需要 inline(或改为 static inline / 仅在 .cc 定义)。
工程示例(协议栈常量)¶
头文件中常被多个翻译单元包含的全局常量:
inline constexpr IPv4Address kIPv4Any{{0, 0, 0, 0}};
inline constexpr Subnet kIPv4EmptySubnet{kIPv4Any, 0};
若去掉 inline,每个 .cc include 该头后可能各生成一份 kIPv4Any,链接失败。
与「内联优化」的关系¶
| 说法 | 是否准确 |
|---|---|
inline = 编译器一定把函数展开到调用处 | ❌ 不准确 |
未写 inline 的函数不会被内联 | ❌ 编译器在 -O2 等级别仍可能内联 |
inline 允许在头文件中放函数/变量定义 | ✅ 标准主语义 |
是否做内联展开由编译器决定;inline 关键字的首要用途是 ODR / 链接,不是性能保证。
与 #define 宏的对比¶
早期常用宏在头文件中「仿函数」以避免链接错误,但缺少类型与作用域检查。现代 C++ 更推荐:
inline int Max(int a, int b) { return a > b ? a : b; }
需要编译期常量时,配合 constexpr 使用,而不是宏。
inline namespace¶
inline namespace v2 {
void Foo();
}
用于版本/ABI 与名称查找(例如标准库实现中的 inline namespace std::__1),与「函数内联优化」不是同一概念。日常写业务代码时较少手写。