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

原因是:

  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 文件,所以 std::vector<int> (这只是一些其他编译成本高昂的模板化构造的示例)只需编译一次,而不是如果我编译了两次 foo.cppbar.cpp 分别地。当然,我上面的理由(3)对于这种情况是无效的:更改 foo(){...} 后,我必须重新编译整个可能很大的文件 foobar.cpp.

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

有帮助吗?

解决方案

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

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

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

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

其他提示

在我看来,每个文件一个函数可能会变得混乱。想象一下,如果 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>

不过,每个文件一个类是个好主意。

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

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

// getShapeColor.h
Color getShapeColor(Shape);

// getTextColor.h
Color getTextColor(Text);

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

话虽这么说,即使在标准库的情况下,拆分各个函数也有一些潜在的好处。首先,编译器可以在以下情况下生成有用的警告: 不安全 使用函数的版本,例如 结构体结构体, ,类似于 g++ 如何警告包含 <iostream.h> 与<iostream>。

另一个优点是我将不再被包含在内而陷入困境 记忆 当我想使用时 记忆移动!

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

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

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

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

但有时每个文件有几千行代码是不必要的。函数本身最多不应该超过几百行代码。永远记住,一个函数应该只做一件事并且保持最小。如果一个函数执行不止一件事,则应将其重构为辅助方法。

拥有多个定义单个实体的源文件也没什么坏处。IE:“ServiceConnection.cpp”“ServiceSettings.cpp”,依此类推。

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

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

如果您正在制作静态库,每个文件一个函数具有技术优势(我想这就是像这样的项目的原因之一) 音乐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++ 名称修饰

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

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

为了 标头 部分,您应该将项目组合成逻辑分组并创建您的 标头 基于此的文件。恕我直言,这似乎而且非常合乎逻辑。

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

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

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

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

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

  3. 使得文件维护变得很痛苦。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top