constexpr 和 inline
inline
inline作用有二:
- 链接可重复(首要):允许函数定义多次,但会合并为一个,避免重定义
- 内联展开提示(次要):建议编译器内联展开,也就是直接把函数嵌入到调用处
inline使用场景:
可重复实现:实现在h,hpp的函数,如果类方法,或直接写进去的函数
(实现在hpp的类里的函数,默认带inline)
/// @file foo.hpp #pragma once class Foo{ int x; // 这个构造函数默认inline Foo(int x){ this->x = x; } // 类里实现的函数,默认 inline,可省 inline int Bar(int y){ return x * y; } } // 全局函数,默认没有 inline,不加 inline 会报错 inline int Bar(int x, int y){ return x * y; } // 若是模板,编译器会自己 inline,可省 template<typename T> T Qux(T a, T b){ return a * b; }内联函数用,但inline只是建议编译器,实际未必展开,如果开优化
-O了不inline也可能展开(所以只是建议,选择权在编译器)/// @file math-tool.hpp #pragma once namespace math_tool{ // 这里用了可能展开,不过实现在hpp里了也不得不inline了(作用1) inline float Sin(float x){ // 调用了某种数学硬件优化库 return arm_sin_f32(x); } // inline 时,声明和实现应在同一头文件。若仅 .hpp 声明 // 但实现放在 .cpp,不触发内联展开优化 // 如下: inline float Cos(float x); }如果展开,就不会跳到Sin再跳到arm_sin_f32,相当于直接把 arm_sin_f32 放在调用处。
像这样调用函数时,栈的跳转可能比函数运算的开销还大(调用函数要压栈、换指针,也是需要时间的),尤其是这样频繁调用的函数,cpu在函数和现场跳来跳去,得不偿失,就需要优化了。
内联展开,相当于 copy 了一份函数,放在调用处,内存可能变大了,这算是优化的空间换时间(?)
constexpr
inline 强调函数的链接,和内联展开优化。而 constexpr 是用于编译器优化:这个变量/函数需要在编译器就算出来。
/// @file test.cpp
// 以下都是全局作用域
constexpr int Foo(int a){
return a * 10;
}
constexpr int a = Foo(4);
// 也是对的,相当于 array[100]
int array[Foo(10)];
const 和 constexpr 变量的区别:
const强制初始化后不可变,但值可能运行时才确定只有全局值确定才放在数据区,不然大概进 RAM
constexpr强制编译期用常量初始化全局 / 静态:一定不占用 RAM
局部:先优化为立即数、不占栈;复杂对象仍可能在栈
constexpr 函数自带 inline:
- 编译期用:编译期算完,作为常量用
- 运行时用:不是编译期能算的常量,退化为 inline 函数
两者关系
其实现在编译器会自动优化,比如 1 + 2 会自动编译期算出 3,而不是放到编译完的程序里,每次运行算一遍。
这样说,inline 如果展开了,编译器再优化了,其实就是 constexpr。但优化不是 inline 做的,inline 更重要的任务是防止重定义。
constexpr 让程序限制死了在编译器能用,指定了要优化这里,所以是编译器优化指令。
inline就算展开优化为了常数,也不能在编译器用。