C++ inline说明符详解_c++格式说明符
inline说明符(inline specifier)在C++17前用于声明一个函数为内联函数, C++17起可用于声明内联变量。“inline”为C++语言的一个关键字。
一 inline 函数
inline说明符在用于函数的“声明说明符序列”时,将函数声明为一个内联函数。
(1)内联函数的作用
- 对于内联函数,C++有可能直接用函数体代码来替代对函数的调用,这一过程称为函数体的内联展开。
- 对于只有几条语句的小函数来说,与函数的调用、返回有关的准备和收尾工作的代码往往比函数体本身的代码要大得多。因此,对于这类简单的、使用频繁的小函数,将之沈明为内联函数可提高运行效率。
(2)注意事项
内联是以代码膨胀复制为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码, 将使程序的总代码量增大,消耗更多的内存空间。
不宜使用内联函数的情况:函数体内代码过长或者有循环语句。这样的情况下,使用内联函数往往开销更大。
(3)隐式内联函数的情况
- 如果函数完全在 class/struct/union 的定义之内定义,且被附着到全局模块(C++20起)的函数是隐式的内联函数,无论它是成员函数还是友元函数。
- 声明有constexpr 的函数是隐式的内联函数。(C++11起)
- 弃置的函数是隐式的内联函数,弃置定义可出现在多个翻译单元中。(C++14起增加了[[deprecated]] 标准属性,用于声明一个函数为弃置函数,比如 [[deprecated]] void func(); C++14前可使用编译器支持的属性,比如对于GCC编译器,可使用 __attribute__ ((deprecated)) 声明)
(4)细节知识
- 在内联函数中,所有函数定义中函数局部静态对象在所有翻译单元中共享(指代相同的定义与某一个翻译单元中的对象)
- 所有函数定义中所定义的类型同样在所有翻译单元中相同。
- inline关键字的本意是作为给优化器的指示器,以指示编译器优先采用“函数的内联替换”而非进行函数调用,即并不执行将控制转移到函数体内的函数调用 CPU 指令,而是代之以执行函数体的一份副本而无需生成调用。这会避免函数调用的开销(传递实参及返回结果),但它可能导致更大的可执行文件,因为函数体必须被复制多次。
- 因为关键词 inline 的含义是非强制的,编译器拥有对任何未标记为 inline 的函数使用内联替换的自由,和对任何标记为 inline 的函数生成函数调用的自由(即最终是否内联是由编译器决定,并不是声明了inline就一定会内联)。但这些优化选择不改变上述关于多个定义和共享静态变量的规则。
二 inline 变量
从C++17起,关键字 inline 对于函数的定义已经变为“允许多次定义”而非“优先内联”,因此该含义扩展到了变量。
inline说明符,在用于具有静态存储期的变量(静态类成员或命名空间作用域变量)的“声明说明符序列”时,将变量声明为内联变量。声明为constexpr的静态成员变量(非命名空间作用域变量)是隐式的内联变量。
class MyClass
{
public:
static constexpr int a{ 10 }; // 隐式的内联变量
};
inline变量的主要作用是即使定义的全局对象被多个文件引用也只会有一个全局对象,即内联变量是唯一的。inline变量将隐式声明为extern(外部链接)。
内联变量消除了将C++代码打包为唯头文件的库的主要障碍。
inline变量和thread_local组合可以为每一个线程定义一个属于线程自己的内联变量(thread_local是C++11增加的关键字)。如:
inline thread_local int thread_self_var = 10;
inline变量为C++17核心语言功能特性,于提案 P0386R2 (见文档:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0386r2.pdf) 中提出。各编译器支持情况见下表:
编译器 | GCC | Clang | MSVC | EDG ecpp | Intel C++ | Embarcadero C++ Builder | Nvidia HPC C++(前 Portland Group/PGI) |
开始支持版本 | 7 | 3.9 | 19.12* | 4.14 | 19.0 | 10.3 | 18.1 |
从C++17标准起,可通过判断宏 __cpp_inline_variables 是否被定义来判断编译器是否支持内联变量,比如:
#include <iostream>
int main(int argc, char *argv[])
{
#ifdef __cpp_inline_variables
std::cout << __cpp_inline_variables << std::endl;
#else
std::cout << "not support" << std::endl;
#endif
return 0;
}
// 可能输出: 201606
三 说明
内联函数或内联变量具有以下性质:
- 内联函数或变量的定义必须在访问它的翻译单元中可达(不一定要在访问点前,即可以前置声明),可以简单这样理解,内联必须定义在头文件中而不是.cpp文件中。
- 带外部链接的内联函数或变量(例如不声明static)拥有下列额外属性:
(1)在程序中可有多次定义,只要每个定义都出现在不同的翻译单元中(对于非静态的内联函数和变量)且所有定义等同即可。例如,内联函数或内联变量可定义于被多个源文件所#include的头文件中。
(2)必须在每个翻译单元中都被声明为inline。
(3)在每个翻译单元中都拥有相同的地址。
四 注意事项
- 如果具有外部链接的内联函数或内联变量在不同翻译单元中的定义不同,则产生的行为未知。
- inline说明符不能用于块作用域内(函数内部)的函数(lambda函数)或变量声明。
- inline说明符不能重声明在翻译单元中已定义为非内联函数或内联变量。
- 隐式生成的成员函数和任何在其首次声明为预置的成员函数,与任何其他在类定义内定义的函数一样是内联的。
- 如果一个内联函数在不同翻译单元中被声明,则其默认实参的积累集合必须在每个翻译单元的末尾相同。
- 在C语言中,内联函数不必在每个翻译单元中声明为inline(但最多一个可以是非inline或extern inline),函数定义不必相同(但如果程序依赖于调用的是哪个函数则行为未定义),且函数局部的静态变量在同一函数的不同定义间不同。