在C++中编写头文件中的函数定义

65

我有一个类,其中包含许多小函数。所谓的小函数是指不进行任何处理而只返回文字值的函数。就像这样:

string Foo::method() const{
    return "A";
}

我创建了一个头文件"Foo.h"和源文件"Foo.cpp"。但是由于函数非常小,我考虑将其放在头文件中。我有以下问题:

  1. 如果我将这些函数定义放在头文件中,是否会有性能或其他问题?我将有很多这样的函数。
  2. 我的理解是,在编译完成后,编译器将扩展头文件并将其放置在包含它的位置。这是正确的吗?
6个回答

73
如果函数很小(你不太可能经常更改它),而且函数可以放到头文件中而不需要包含许多其他头文件(因为你的函数依赖于它们),那么这样做是完全有效的。如果你将它们声明为extern inline,那么编译器必须为每个编译单元分配相同的地址: :
inline string method() {
    return something;
}

如果成员函数是在类内部定义的,则它们会隐式地成为内联函数。同样的事情也适用于它们:如果可以将它们放入头文件而没有麻烦,确实可以这样做。

因为函数的代码被放置在头文件中并且可见,编译器能够将对它们的调用内联,即直接将函数的代码放在调用点(不仅仅是因为你在前面加了inline关键字,而更多的是因为编译器决定这样做)。这可能会导致性能提升,因为编译器现在可以看到参数与函数局部变量匹配的位置,以及参数彼此之间没有别名 - 最后但并非最不重要的是,不再需要函数框架分配。

我的理解是,在编译完成时,编译器将扩展头文件并将其放置在其中包含的位置。正确吗?

是的,这是正确的。该函数将在包括其头文件的每个地方进行定义。编译器将通过消除其他实例来确保只将一个实例放入结果程序中。


谢谢。所有这些小函数都是虚函数。这会对内联有什么影响吗?我认为在源文件中编写函数体并将其标记为内联要比直接在头文件中编写要好。如果所有这些函数都在头文件中定义,我担心头文件可读性会降低。 - Navaneeth K N
但我同意你的观点:我经常不愿将代码放入头文件中,因为当我修改它时,它会影响到调用它的所有代码,并且通常在头文件中定义需要包含至少一个其他的代码依赖的 header。尽管并非总是如此,例如对于简单的 getter 函数,我会把它们放在那里。 - Johannes Schaub - litb
1
编译器不会内联虚函数,虚函数的整个关键就在于它们将通过类 vtable 进行调用,以便它们可以被重写。 - Ismael
1
他在追问是否从理论上讲是可能的,我认为。如果编译器知道调用时指向对象的动态类型,它就可以做到。 - Johannes Schaub - litb
如果函数被“内联”,那么在生成的程序中会有多个实例,对吗? - Alcott
显示剩余3条评论

11

根据您所使用的编译器及其设置,它可能会执行以下任何操作:

  • 它可能会忽略inline关键字(它只是向编译器发出的提示,而非命令),并生成独立的函数。如果您的函数超过了编译器特定的复杂度阈值,例如嵌套循环太多,则可能会这样做。
  • 它可能会决定将独立的函数作为内联展开的良好候选项。

在许多情况下,编译器比您更有能力确定函数是否应该内联,因此没有必要猜测它。我喜欢在类中有许多小函数时使用隐式内联,只是因为将实现放在类中很方便。但对于较大的函数效果不佳。

另一件需要记住的事情是,如果您正在导出一个DLL /共享库中的类(我认为这不是一个好主意,但人们仍然这样做),您需要非常小心内联函数。如果建立DLL的编译器决定内联一个函数,您就会面临以下几个潜在问题:

  1. 使用该DLL构建程序的编译器可能决定不对该函数进行内联,因此会生成对不存在的函数的符号引用,从而无法加载DLL。
  2. 如果您更新了DLL并更改了内联函数,则客户端程序仍将使用该函数的旧版本,因为该函数已内联到客户端代码中。

好的回复。谢谢 :) 顺便问一下,我的函数是虚拟的,内联会有什么影响吗? - Navaneeth K N
2
虚函数不能被内联,它们需要通过vtable中的指针引用。我从未尝试过,但编译器应该会忽略内联或者抱怨它。 - Ferruccio
1
虚函数可以在编译时已知类型的情况下进行内联。但实际上这种情况非常罕见。 - Tom

5

由于头文件中的实现被隐式地内联,因此性能将会提高。就像你提到的一样,如果你的函数很小,内联操作对你非常有益。

你所说的编译器也是正确的。除了内联之外,在头文件或.cpp文件中的代码对编译器来说没有区别。


2
  1. 如果你的函数非常简单,可以将它们变为内联函数,这样你就必须把它们放入头文件中。除此之外,任何习惯都只是习惯而已。

  2. 是的,编译器在遇到 #include 语句时会展开头文件。


2

这取决于您所应用的编码标准,但:

小型函数没有循环和其他内容时,应进行内联处理以获得更好的性能(但代码略大,对于某些受限或嵌入式应用程序非常重要)。

如果在头文件中有函数体,则默认情况下会进行内联处理(这对于速度来说是一件好事)。

在编译器创建对象文件之前,会调用预处理器(gcc 的 -E 选项),并将结果发送给编译器,后者将代码转换为对象。

因此,简短的答案是:

-- 在头文件中声明函数对于速度来说很好(但空间不够用)--


1
C++不会抱怨,但一般情况下你不应该这样做。当你#include一个文件时,被包含文件的整个内容将插入到包含点。这意味着你放在头文件中的任何定义都会被复制到每个包含该头文件的文件中。对于小项目来说,这可能不是什么问题。但对于大型项目来说,这可能导致编译时间变得更长(因为每次遇到相同的代码都会重新编译),并且可能会显著增加可执行文件的大小。如果你在代码文件中更改了定义,只需要重新编译那个.cpp文件。如果你在头文件中更改了定义,需要重新编译包含该头文件的每个代码文件。一个小小的更改可能会导致你必须重新编译整个项目!有时可以为不太可能更改的琐碎函数(例如,函数定义只有一行)做出例外。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接