内联函数

内联函数

函数的使用提供了许多好处,包括:

  • 函数内部的代码可以重用。

  • 更改或更新函数中的代码(只需改一次)要比在多处更改重复的代码容易。重复代码是导致效率低下和程序错误的一个因素之一。

  • 它使您的代码更易于阅读和理解,因为您不需要查看它的实现来理解它的作用(前提是有合理的命名和注释)。

  • 函数提供类型检查以确保函数调用参数与函数参数匹配(类似函数的宏[function-like macros]不执行此操作,这可能导致错误)。

  • 函数使您的程序更容易调试。

但是,函数的一个主要缺点是每次调用函数时,都会产生一定的性能开销。这是因为CPU必须存储它正在执行的当前指令的地址(这样它才知道之后该从哪里返回)以及其他寄存器,所有函数参数必须被创建并赋值,并且程序必须跳转到新的位置执行。因此就地(in-place)编写的代码要快得多。

对于执行复杂任务的函数,与函数运行所花费的时间相比,函数调用的开销通常是微不足道的。但是,对于常用的短函数(不超过几行),进行函数调用所需的时间通常比实际执行函数代码所需的时间多得多。这可能导致严重的性能损失。

C++提供了一种将函数的​​优点与就地编写的代码速度相结合的方法:内联函数(inline function)。inline 关键字用于请求编译器把你的函数作为内联函数。当编译器编译你的代码时,所有内联函数都会就地展开 – 也就是说,函数调用被替换为函数本身内容的副本,这会删除函数调用开销!缺点是因为内联函数在每次函数调用时就地扩展,这可能会使编译的代码变得更大,特别是如果内联函数很长或对内联函数有很多调用。

比如下面的代码段:

1
2
3
4
5
6
7
8
9
10
11
int min(int x, int y)
{
return x > y ? y : x;
}

int main()
{
std::cout << min(5, 6) << '\n';
std::cout << min(3, 2) << '\n';
return 0;
}

该程序调用了min()函数两次,产生了两次函数调用的开销。然而min()函数是一个很短的函数,把它作为内联函数是最好的选择。

1
2
3
4
inline int min(int x, int y)
{
return x > y ? y : x;
}

现在,当程序编译main()时,两次min()函数调用被就地展开,就好像main()已经像这样编写:

1
2
3
4
5
6
int main()
{
std::cout << (5 > 6 ? 6 : 5) << '\n';
std::cout << (3 > 2 ? 2 : 3) << '\n';
return 0;
}

这将使程序执行得更快,代价是编译代码略大。

由于代码膨胀的可能性,一般把那些经常在循环内部被调用且内部没有分支跳转的短函数(不超过几行)作为内联函数。另外请注意,inline关键字只是一个建议,用于告知编译器你希望每遇到此函数的调用时,都将该函数内容就地展开 – 但如果你试图内联一个冗长的函数,编译器很可能会忽略你的请求。

如今的编译器现在非常擅长自动内联函数 – 在大多数情况下比人类做得更好。即使您没有将函数标记为内联,编译器也会自动内联它认为可以提升性能的函数。因此,在大多数情况下,没有特意使用inline关键字的必要。让编译器为您处理内联函数。

规则:了解有inline函数这么一个东西,但现代编译器会适当地为您自动内联,所以没必要使用inline关键字。

内联函数是“每个程序只能有一个定义”的例外。

在前面的章节中,我们已经注意到您不应该在头文件中实现函数(带有外部链接),因为当这些头文件包含在多个.cpp文件中时,函数定义将被复制到多个.cpp文件中。然后将编译这些文件,并且链接器将抛出错误,因为它会注意到您已经多次定义了相同的函数。

但是,内联函数不受“每个程序只能有一个定义”的规则的限制,因为内联函数实际上并不会被当成一个函数来编译 - 因此,当链接器链接到多个文件在一起时不会产生冲突。

这一点在现在看起来是无聊琐碎,但下一章我们会介绍一种新的函数(成员函数),它充分利用了这一点。

即便是使用内联函数,你也不应该在头文件中定义全局函数。