【问题标题】:Should I put many functions into one file? Or, more or less, one function per file?我应该将许多功能放在一个文件中吗?或者,或多或少,每个文件一个函数?
【发布时间】:2010-10-06 13:53:06
【问题描述】:

我喜欢组织我的代码,所以理想情况下我希望每个文件一个类,或者当我有非成员函数时,每个文件一个函数。

原因是:

  1. 当我阅读代码时,我总是 知道我应该在哪个文件中找到 某些功能或类。

  2. 如果是一类或非成员 每个头文件的功能,然后我不会 当我包括一团糟 include 一个头文件。

  3. 如果我对一个函数做一个小的改动,那么只有那个函数需要重新编译。

但是,将所有内容拆分为多个头文件和多个实现文件会大大减慢编译速度。在我的项目中,大多数函数访问一定数量的模板化其他库函数。这样代码将被一遍又一遍地编译,每个实现文件一次。目前在一台机器上编译我的整个项目需要 45 分钟左右。大约有 50 个目标文件,每个目标文件都使用相同的昂贵编译头文件。

也许,每个头文件文件有一个类(或非成员函数)是否可以接受,但将许多或所有这些函数的实现放入一个实现文件,如下例所示?

// foo.h
void foo(int n);

// bar.h
void bar(double d);

// foobar.cpp
#include <vector>
void foo(int n) { std::vector<int> v; ... }
void bar(double d) { std::vector<int> w; ... }

同样,优点是我可以只包含 foo 函数或只包含 bar 函数,整个项目的编译会更快,因为 foobar.cpp一个 文件,所以 @ 987654324@(这只是其他一些昂贵的编译模板结构的示例)必须只编译一次,而不是如果我分别编译 foo.cppbar.cpp 则编译两次。当然,我上面的理由 (3) 不适用于这种情况:在更改 foo(){...} 之后,我必须重新编译整个可能很大的文件 foobar.cpp

我很好奇你的意见是什么!

【问题讨论】:

    标签: c++ build-process compilation code-organization file-organization


    【解决方案1】:

    恕我直言,您应该将项目组合成逻辑分组并在此基础上创建文件。

    当我编写函数时,通常有六个左右紧密相关的函数。我倾向于将它们放在一个头文件和实现文件中。

    当我编写类时,我通常将自己限制为每个头文件和实现文件一个重量级类。我可能会添加一些便利功能或小型辅助类。

    如果我发现一个实现文件长达数千行,这通常表明其中有太多内容,我需要将其分解。

    【讨论】:

      【解决方案2】:

      在我看来,每个文件的一个函数可能会变得混乱。想象一下,如果 POSIX 和 ANSI C 标头的制作方式相同。

      #include <strlen.h>
      #include <strcpy.h>
      #include <strncpy.h>
      #include <strchr.h>
      #include <strstr.h>
      #include <malloc.h>
      #include <calloc.h>
      #include <free.h>
      #include <printf.h>
      #include <fprintf.h>
      #include <vpritnf.h>
      #include <snprintf.h>
      

      每个文件一个类是个好主意。

      【讨论】:

      • 对我来说听起来很棒,再也不用查找函数在哪个头文件中了......但他们也可以有方便的头文件,其中包含 string.h 中的所有字符串函数等等.另一方面,如果使用 strlen,编译器可以假设包含“strlen.h”(当然有警告)
      【解决方案3】:

      我们使用每个文件一个外部函数的原则。但是,在此文件中,可能还有其他几个在未命名的命名空间中用于实现该功能的“帮助”函数。

      根据我们的经验,与其他一些 cmets 不同,这有两个主要好处。首先是构建时间更快,因为只需要在修改其特定 API 时重新构建模块。第二个好处是,通过使用通用的命名方案,无需花时间搜索包含您要调用的函数的标头:

      // getShapeColor.h
      Color getShapeColor(Shape);
      
      // getTextColor.h
      Color getTextColor(Text);
      

      我不同意标准库是不为每个文件使用一个(外部)函数的一个很好的例子。标准库永远不会改变并且具有定义良好的接口,因此上述几点都不适用于它们。

      话虽如此,即使在标准库的情况下,拆分各个函数也有一些潜在的好处。第一个是当使用 unsafe 版本的函数时,编译器可能会生成有用的警告,例如strcpy vs. strncpy,类似于 g++ 过去如何警告包含 vs.

      另一个好处是,当我想使用 memmove 时,我将不再被包含 memory 所困扰!

      【讨论】:

        【解决方案4】:

        如果你正在制作一个静态库,每个文件一个函数具有技术优势(我想这就是像 Musl-libc 项目这样的项目遵循这种模式的原因之一)。

        静态库以对象文件粒度链接,因此如果您有一个静态库libfoobar.a 由*组成:

         foo.o
             foo1
             foo2
         bar.o
             bar
        

        然后,如果您链​​接 bar 函数的库,则 bar.o 存档成员将被链接,但 foo.o 成员不会链接。如果您为foo1 链接,那么foo.o 成员将被链接,从而引入可能不必要的foo2 函数。

        可能还有其他方法可以防止链接不需要的函数(-ffunction-sections -fdata-sections--gc-sections),但每个文件一个函数可能是最可靠的。


        • 为了简单起见,我在这里忽略了 C++ 名称修改

        【讨论】:

          【解决方案5】:

          您可以将您的一些函数重新声明为一个或多个类的静态方法:这为您提供了一个机会(也是一个很好的借口)将其中的几个分组到一个源文件中。

          如果该源文件与目标文件是一对一的,并且链接器链接整个目标文件,那么在一个源文件中具有或不具有多个函数的一个很好的理由:如果可执行文件可能需要一个函数而不需要另一个函数,然后将它们放在单独的源文件中(以便链接器可以链接一个而不链接另一个)。

          【讨论】:

          • 将函数放在命名空间中比类的静态成员更好。使用函数的语法相同,但意图更明确。
          • 大多数链接器只会拉取最终可执行文件中已调用的函数,因此我认为优化链接器没有多大意义。
          【解决方案6】:

          我的一位老编程教授建议每隔几百行代码就分解模块以提高可维护性。我不再使用 C++ 开发,但在 C# 中,我将自己限制为每个文件一个类,只要没有与我的对象无关的文件大小,文件的大小就无关紧要。您可以使用#pragma 区域来优雅地减少编辑器空间,不确定 C++ 编译器是否有它们,但如果有,那么肯定会使用它们。

          如果我仍在使用 C++ 编程,我会使用每个文件的多个函数按用途对函数进行分组。所以我可能有一个名为“Service.cpp”的文件,其中包含一些定义该“服务”的函数。每个文件有一个函数反过来会导致遗憾地以某种方式重新回到您的项目中。

          虽然有时每个文件都不需要几千行代码。函数本身最多不应该超过几百行代码。永远记住,一个函数应该只做一件事并保持最小。如果一个函数做不止一件事,它应该被重构为辅助方法。

          拥有多个定义单个实体的源文件也没有什么坏处。即:'ServiceConnection.cpp' 'ServiceSettings.cpp'等等。

          有时,如果我创建一个对象,并且它拥有其他对象,我会将多个类组合到一个文件中。例如,包含“ButtonLink”对象的按钮控件,我可能会将其组合到 Button 类中。有时我不会,但这是“当下的偏好”决定。

          做最适合你的事情。在较小的项目上尝试不同的风格会有所帮助。希望对你有所帮助。

          【讨论】:

            【解决方案7】:

            我还尝试在每个文件的函数中拆分文件,但它有一些缺点。有时函数往往会变得比它们需要的大(你不想每次都添加一个新的 .c 文件),除非你勤奋地重构你的代码(我不是)。

            目前我将一到三个函数放在一个 .c 文件中,并将所有 .c 文件分组为一个目录中的一个功能。对于头文件,我有Funcionality.hSubfunctionality.h,这样我可以在需要时一次包含所有函数,如果不需要整个包,我可以只包含一个小的实用程序函数。

            【讨论】:

              【解决方案8】:

              对于 header 部分,您应该将项目组合成逻辑分组,并在此基础上创建 header 文件。恕我直言,这似乎并且非常合乎逻辑。

              对于 source 部分,您应该将每个函数实现放在一个单独的 source 文件中(在这种情况下静态函数是例外)。起初这似乎不合逻辑,但请记住,编译器知道函数,但链接器只知道 .o 和 .obj 文件及其导出的符号。这可能会显着改变输出文件的大小,这对于嵌入式系统来说是一个非常重要的问题。

              检查 glibc 或 Visual C++ CRT 源代码树...

              【讨论】:

              • 哈,我想反之亦然。我认为没有理由将大量函数放入 HEADER 文件中,因为这样您就会获得编译依赖项。但是将很多功能实现放入 SOURCE 对我来说是有意义的......
              • 您不必相信我的话,只需检查 Visual c++ crt 源代码或 glibc...
              【解决方案9】:

              我可以看到您的方法有一些优点,但也有几个缺点:

              1. 包含一个包是一场噩梦。您最终可以使用 10-20 个包含来获得所需的功能。例如,想象一下如果 STDIO 或 StdLib 是以这种方式实现的。

              2. 浏览代码会有点痛苦,因为通常滚动文件比切换文件更容易。显然太大的文件很难,但即使使用现代 IDE,也很容易将文件折叠成您需要的内容,并且其中很多都有功能快捷列表。

              3. 让文件维护很痛苦。

              4. 我非常喜欢小功能和重构。当您增加开销时(创建一个新文件,将其添加到源代码管理......),它会鼓励人们编写更长的函数,而不是将一个函数分成三个部分,您只需制作一个大的部分。

                李>

              【讨论】:

              • Re: #4: 也许您可以将自己限制为每个文件一个“公共”函数,并允许自己在该文件中拥有尽可能多的静态函数。这样,如果我正在使用您的代码并想查找一个函数,我就知道在哪里查找。此外,您保留“此文件中的所有内容都与此单个功能相关/相关”的概念。
              猜你喜欢
              • 2020-06-25
              • 2011-05-04
              • 1970-01-01
              • 2013-07-23
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-08-21
              • 1970-01-01
              相关资源
              最近更新 更多