【问题标题】:Writing function definition in header files in C++在 C++ 的头文件中编写函数定义
【发布时间】:2010-10-01 23:43:25
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: c++ performance header-files


    【解决方案1】:

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

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

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

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

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

    【讨论】:

      【解决方案2】:

      性能会有所提高,因为头文件中的实现是隐式内联的。正如你提到的,你的函数很小,内联操作对你来说非常有益。

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

      【讨论】:

        【解决方案3】:

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

        headera.h

        inline string method() {
            return something;
        }
        

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

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

        我的理解是编译完成后,编译器会展开头文件,放到包含的地方。对吗?

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

        【讨论】:

        • 谢谢。所有这些小功能都是虚拟的。这会对内联产生任何影响吗?而且我认为在源文件中编写函数体并将其标记为内联比直接在标题中编写要好。如果所有这些函数都在那里定义,恐怕头文件的可读性会降低。
        • 如果编译器可以找出虚函数调用的方向,它也可以内联:b *b_ = new d; doit(b_); // 如果它内联 doit,它将看到 b_ 是 d。然后它可以像在 d 中一样内联虚函数定义的代码。虚拟使事情变得更加困难,但并非不可能
        • 但我同意你的观点:我经常不愿意将代码放入标题中,因为当我更改它时,它会影响所有调用它的代码,并且通常在标题中定义至少需要包括代码所依赖的另一个标头。 (并非总是如此。对于简单的吸气剂,我把它们放在那里)。
        • 编译器不会内联虚函数,虚函数的全部意义在于它们会通过类vtable调用,所以它们可以被覆盖。
        • 我想,他在追求理论上是否可行。如果编译器知道在调用点指向的对象的动态类型,编译器就可以做到。
        【解决方案4】:

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

        • 它可能会忽略 inline 关键字(它 只是对编译器的提示,而不是 命令)并生成独立的 职能。如果您的 函数超出了编译器依赖 复杂度阈值。例如太多 嵌套循环。
        • 它可能比你的单机决定 函数是一个很好的候选 内联扩展。

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

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

        1. 构建程序的编译器 使用 DLL 可能决定不 内联函数,所以它会 生成对 a 的符号引用 不存在的功能和 DLL 不会加载。
        2. 如果您更新 DLL 并更改 内联函数,客户端程序 仍将使用旧版本 该函数的自函数 内联到客户端代码中。

        【讨论】:

        • 很好的回复。谢谢 :) 顺便说一句,我的函数是虚拟的,内联它会有什么不同吗?
        • 虚函数不能内联,需要通过vtable中的指针来引用。我从未尝试过,但编译器应该要么忽略内联,要么抱怨它。
        • 如果类型在编译时已知,则可以内联虚拟函数。这在实践中非常罕见。
        【解决方案5】:

        您应该使用内联函数。阅读此Inline Functions 以获得更好的理解和所涉及的权衡。

        【讨论】:

        • 链接不再有效
        • 这真的不适用于问题,问题不是“如何提高性能?”之类的。更合适的说法是它被称为“内联函数”,并提供一个链接来解释其后果。
        【解决方案6】:

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

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

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

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

        所以简短的答案是:

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

        【讨论】:

          【解决方案7】:
          1. 如果您的函数如此简单,请将它们内联,无论如何您都必须将它们粘贴在头文件中。除此之外,任何约定都只是约定。

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多