65

我有一个有很多小功能的类。小函数是指不进行任何处理而只返回文字值的函数。就像是:

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

我创建了一个头文件“Foo.h”和源文件“Foo.cpp”。但是由于函数很小,我正在考虑将它放在头文件本身中。我有以下问题:

  1. 如果我将这些函数定义放在头文件中,是否有任何性能或其他问题?我会有很多这样的功能。
  2. 我的理解是编译完成后,编译器会展开头文件并将其放在包含的位置。那是对的吗?
4

6 回答 6

71

如果函数很小(您经常更改它的机会很低),并且如果该函数可以放入头文件中而不包含无数其他头文件(因为您的函数依赖于它们),那么这样做是完全有效的。如果将它们声明为 extern inline,则编译器需要为每个编译单元提供相同的地址:

headera.h

inline string method() {
    return something;
}

成员函数是隐式内联的,前提是它们在其类中定义。对它们来说也是如此:如果它们可以毫不费力地放入标题中,那么您确实可以这样做。

因为函数的代码放在头部并且可见,所以编译器能够内联调用它们,即直接将函数的代码放在调用位置(不是因为你在它之前放置了inline,而是更多因为编译器会以这种方式决定。仅将 inline 放入是对编译器的提示)。这可以提高性能,因为编译器现在可以看到参数与函数局部变量匹配的位置,以及参数不相互别名的位置 - 最后但并非最不重要的一点是,不再需要函数帧分配。

我的理解是编译完成后,编译器会展开头文件并将其放在包含的位置。那是对的吗?

对,那是正确的。该函数将在您包含其标题的每个地方定义。编译器只关心将它的一个实例放入结果程序中,通过消除其他实例。

于 2009-01-17T14:44:05.473 回答
12

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

  • 它可能会忽略 inline 关键字(它只是对编译器的提示,而不是命令)并生成独立函数。如果您的函数超过编译器相关的复杂度阈值,它可能会这样做。例如太多的嵌套循环。
  • 它可能决定您的独立函数是否适合内联扩展。

在许多情况下,编译器在确定一个函数是否应该被内联方面比你更有优势,所以没有必要再猜测它。当一个类有很多小函数时,我喜欢使用隐式内联,只是因为在类中有实现很方便。这对于较大的功能来说效果不佳。

要记住的另一件事是,如果您要在 DLL/共享库中导出一个类(恕我直言,这不是一个好主意,但人们无论如何都会这样做),您需要非常小心内联函数。如果构建 DLL 的编译器决定一个函数应该被内联,那么你有几个潜在的问题:

  1. 使用 DLL 构建程序的编译器可能决定不内联函数,因此它将生成对不存在的函数的符号引用,并且 DLL 将不会加载。
  2. 如果您更新 DLL 并更改内联函数,客户端程序仍将使用该函数的旧版本,因为该函数已内联到客户端代码中。
于 2009-01-17T15:18:42.003 回答
5

由于头文件中的实现是隐式内联的,因此性能会有所提高。正如您提到的,您的功能很小,内联操作将对您非常有益,恕我直言。

您对编译器的看法也是正确的。编译器(除了内联)在头文件或.cpp文件中的代码之间没有区别。

于 2009-01-17T14:41:15.387 回答
2
  1. 如果您的函数如此简单,请将它们内联,无论如何您都必须将它们粘贴在头文件中。除此之外,任何约定都只是——约定。

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

于 2009-01-17T14:42:24.337 回答
2

这取决于适用于您的情况的编码标准,但是:

应该内联没有循环和其他任何内容的小函数以获得更好的性能(但代码稍大 - 对于某些受限或嵌入式应用程序很重要)。

如果您在标头中有函数的主体,则默认情况下将拥有它 inline(d) (在速度方面这是一件好事)。

在编译器创建目标文件之前,调用预处理器(gcc 的 -E 选项)并将结果发送到编译器,编译器使用代码创建对象。

所以更简短的答案是:

-- 在 header 中声明函数有利于速度(但不利于空间) --

于 2009-01-17T14:49:04.933 回答
1

如果你这样做,C++ 不会抱怨,但一般来说,你不应该抱怨。

当您#include 一个文件时,包含文件的全部内容将插入到包含点。这意味着您放入标头的任何定义都会复制到包含该标头的每个文件中。

对于小型项目,这可能不是什么大问题。但是对于较大的项目,这可能会使编译时间变得更长(因为每次遇到相同的代码都会重新编译),并且可能会大大增加可执行文件的大小。如果您更改代码文件中的定义,则只需重新编译该 .cpp 文件。如果您对头文件中的定义进行更改,则需要重新编译包含该头文件的每个代码文件。一个小改动可能会导致您不得不重新编译整个项目!

有时对于不太可能更改的琐碎函数(例如,函数定义只有一行)会例外。

来源: http: //archive.li/ACYlo(learncpp.com 上第 1.9 章的上一版本)

于 2016-12-05T05:57:01.920 回答