文章

内联函数与constexpr函数

内联函数减少调用开销,提升效率;constexpr函数在编译期求值,提高性能和常量表达能力。

内联函数与constexpr函数

内联函数与constexpr函数

内联函数

C++ 中的 内联函数(inline function) 是一种用于提升小函数执行效率的技术,通过在编译阶段将函数调用处替换为函数体代码,从而避免函数调用带来的开销(如压栈、跳转等)。

基本语法

1
2
3
inline 返回类型 函数名(参数列表) {
    // 函数体
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
using namespace std;

inline int add(int a, int b) {
    return a + b;
}

int main() {
    int x = 3, y = 5;
    cout << "Sum: " << add(x, y) << endl;
    return 0;
}

经过内联优化后,add(x, y) 会被直接替换为其函数体 x + y,所以主函数大致会被编译器重写为这样:

1
2
3
4
5
int main() {
    int x = 3, y = 5;
    std::cout << "Sum: " << (x + y) << std::endl;
    return 0;
}

注意:这只是逻辑上的等价替换,编译器最终生成的机器码可能远比这个复杂,也会受编译选项影响(如 -O2, -O3 优化级别)

使用内联函数的优点

  • 减少函数调用开销(尤其适用于频繁调用的短小函数)

  • 增强可读性,像宏那样替代代码块,但具备类型检查与作用域管理

注意事项

  1. 适合短小函数
    • 太复杂的函数可能不会被编译器内联(即使你写了 inline
  2. 编译器决定是否内联
    • inline 只是建议,现代编译器可能根据优化策略选择是否真正内联
  3. 避免在头文件中滥用
    • 多处定义同一函数时应使用 inline 以避免链接冲突(特别在头文件中定义函数时)
  4. 不能递归内联
    • 编译器通常不会对递归函数内联
情况是否建议内联
函数体非常短(如 1~3 行)✅ 适合
函数体较复杂(有循环/逻辑分支)❌ 不建议
函数频繁被调用✅ 适合
函数调用频率低/编译时间敏感❌ 不建议
需要跨文件调用(函数放 .cpp 文件中)❌ 不能内联(必须头文件中定义)

ODR(One Definition Rule)

C++ 要求 每个函数或变量在整个程序中只能有一个定义(即 ODR:One Definition Rule)。如果你把函数定义(非声明!)放在头文件中,并在多个 .cpp 文件中 #include 了这个头文件,就相当于在多个地方都定义了一遍同一个函数。

这就会导致 链接错误(multiple definition)

1
error: multiple definition of 'xxx'; first defined here...

将函数标记为 inline,就告诉编译器和链接器:

“这个函数即使在多个编译单元中定义了,只要定义内容相同,是可以共存的。”

这是 C++ 标准中特意为支持头文件中定义函数而设计的机制。

类与内联函数

类中定义的函数默认是内联的,示例:

1
2
3
4
5
6
class MyClass {
public:
    int getX() const { return x; }  // 在类定义中写了函数体,就是内联函数
private:
    int x = 10;
};

这个 getX() 函数是一个内联成员函数,等价于写在类外并加上 inline

1
2
3
4
5
6
7
8
9
10
class MyClass {
public:
    int getX() const;
private:
    int x = 10;
};

inline int MyClass::getX() const {
    return x;
}

三种写法都可以让函数成为“内联函数”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 写法一:类内定义(自动内联)
class A {
    int get() const { return 1; }
};

// 写法二:类外定义 + inline
class B {
    int get() const;
};
inline int B::get() const { return 2; }

// 写法三:类内声明 + inline 关键字(可选)
class C {
    inline int get() const { return 3; } // inline 是可加可不加
};

constexpr 函数

constexpr 函数是 C++11 引入的一种函数类型,用于 在编译期就能求值的函数。这对于写高效、类型安全的 常量表达式计算逻辑 非常有用。

基本概念

1
2
3
constexpr 返回类型 函数名(参数列表) {
    // 函数体必须是能在编译期求值的代码
}

当所有参数是常量表达式时,constexpr 函数可以在编译期直接被求值,就像常量一样。

示例 1:普通的 constexpr 函数

1
2
3
4
5
6
7
8
9
10
11
12
constexpr int square(int x) {
    return x * x;
}

int main() {
    constexpr int a = square(5);  // 编译期计算,a = 25
    //  编译期常量计算:提高性能,没有运行时调用开销,可以用于常量上下文(比如数组大小、switch 的 case 标签等)
    
    int b = 10;
    int c = square(b);           // 运行期计算(因为 b 不是常量)
    // 即使 square 是 constexpr,它也可以像普通函数一样在运行时调用,这让函数可以在两种场景下复用,而不是再写两个版本(一个宏,一个普通函数)
}

可以把 constexpr 理解成:带有“编译期计算能力”的普通函数。

示例 2:配合数组长度使用

1
2
3
constexpr int len() { return 5; }

int arr[len()];  // OK,len() 是常量表达式

使用条件(C++11/C++14)

constexpr 函数必须满足以下条件,才能在编译期求值:

  • 函数体必须是可计算的常量表达式
  • 所有参数也必须是常量(才能启用编译期求值)
  • 函数体不能有:
    • 非常量变量定义(如 int x = rand();
    • 运行期条件判断或循环(C++14 起部分限制放宽)
  • C++14 开始支持更复杂的语句,如 iffor、递归等

与 inline 的关系

  • constexpr 函数自动隐式为 inline,所有 constexpr 函数都会自动视为 inline 函数,这样可以放心地在头文件中定义它们同时支持编译期求值与多文件包含,避免链接冲突。
  • inline 函数并不能保证编译期求值,它只影响链接与调用方式

与 const 对比

特性constexprconst
定义阶段编译期常量可以是运行期常量
用于函数可以用于函数不能用于函数
编译期计算支持不支持
影响范围值 & 函数体

constexpr / inline 函数 vs 宏(#define

宏是简单的文本替换,没有类型检查、没有作用域控制、容易出错;而 inline / constexpr 函数是类型安全、作用域明确、支持调试和优化的现代写法,几乎在所有场景下都优于宏。

特性/维度#defineinline / constexpr 函数
编译阶段预处理器阶段(字符串替换)编译器阶段(语义完整,类型检查)
是否有作用域❌ 没有作用域✅ 有作用域,像正常函数一样
类型检查❌ 没有类型检查✅ 有完整类型检查
调试支持❌ 不易调试(无法设置断点)✅ 可调试,可单步进入
多次定义是否报错❌ 不报错,但可能出现副作用inline/constexpr 函数合法
函数特性支持(递归等)❌ 不支持constexpr 可递归(C++14 起)
命名空间支持❌ 不支持✅ 支持命名空间和类作用域
是否有副作用风险⚠️ 高风险:参数可能重复求值✅ 无副作用:参数求值一次
推荐程度🚫 尽量避免✅ 推荐使用

示例1

宏:有副作用、无类型检查

1
2
3
4
5
#define SQUARE(x) ((x) * (x))

int a = SQUARE(5);     // OK:结果是 25
int b = SQUARE(1 + 2); // ⚠️ 结果是 ((1 + 2) * (1 + 2)) = 9(正确)
int c = SQUARE(i++);   // ❌ 有副作用!i 会被自增两次

constexpr 函数:安全、可调试、可递归

1
2
3
4
5
6
7
constexpr int square(int x) {
    return x * x;
}

int a = square(5);     // OK:结果是 25
int b = square(1 + 2); // OK:结果是 9
int c = square(i++);   // ✅ i++ 只执行一次(虽然可能不该这样写)

示例2

类型检查防错能力(宏没有)

1
2
3
4
5
6
7
8
#define MAX(x, y) ((x) > (y) ? (x) : (y))

constexpr int max(int x, int y) {
    return x > y ? x : y;
}

int x = MAX("hello", 5);  // ❌ 编译不报错,但运行出错
int y = max("hello", 5);  // ✅ 编译报错:类型不兼容
本文由作者按照 CC BY 4.0 进行授权