我应该把很多功能放到一个文件中吗?或者,或多或少,每个文件一个函数?

时间:2009-02-10 05:20:09

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

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

原因是:

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

  2. 如果是一个班级或一个非会员 每个头文件的功能,然后我不会 我什么时候包括一团糟 include一个头文件。

  3. 如果我对某个函数进行了少量更改,则只需要重新编译该函数。

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

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

    // 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.cpp和{{1}分开。当然,我上面的原因(3)对于这种情况是无效的:刚刚更改foo(){...}后,我必须重新编译整个可能很大的文件bar.cpp

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

9 个答案:

答案 0 :(得分:9)

恕我直言,你应该将项目组合成逻辑分组并根据它创建你的文件。

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

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

如果我发现一个实现文件长达数千行,那通常表明那里有太多的东西,我需要将其分解。

答案 1 :(得分:8)

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

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

答案 2 :(得分:4)

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

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

// getShapeColor.h
Color getShapeColor (Shape);

// getTextColor.h
Color getTextColor (Text);

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

话虽如此,即使在标准库的情况下,分割各个功能也有一些潜在的好处。首先,当使用不安全版本的函数时,编译器可以生成有用的警告,例如。 strcpy vs strncpy ,与g ++用于警告包含vs的方式类似。

另一个优点是,当我想使用 memmove 时,我不会被包含内存所困扰!

答案 3 :(得分:1)

您可以将某些功能重新声明为一个或多个类的静态方法:这为您提供了一个机会(并且是一个很好的借口),可以将其中的几个组合成一个源文件。< / p>

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

答案 4 :(得分:1)

我还尝试在每个文件的函数中拆分文件,但它有一些缺点。有时函数往往会比它们需要的更大(你不想每次都添加一个新的c文件),除非你努力重构你的代码(我不是)。目前,我将1到3个函数放在c文件中,并将所有c文件分组以获取目录中的功能。对于头文件,我有Funcionality.hSubfunctionality.h,这样我可以在需要时立即包含所有函数,或者只需要一个小的实用函数(如果不需要整个包)。

答案 5 :(得分:1)

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

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

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

拥有定义单个实体的多个源文件永远不会受到伤害。即:'ServiceConnection.cpp''ServiceSettings.cpp',依此类推。

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

做最适合你的事情。在较小的项目上尝试一点不同的样式可以提供帮助。希望这能帮到你一点。

答案 6 :(得分:1)

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

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

Checkout glibc或Visual C ++ CRT源代码树...

答案 7 :(得分:1)

我可以看到你的方法有一些优点,但有几个缺点。

1)包括一个包是噩梦。您最终可以获得10-20个包含以获得所需的功能。例如,如果以这种方式实现STDIO或StdLib,则为image。

2)浏览代码会有点痛苦,因为通常滚动文件比切换文件更容易。显然文件太大很难,但即使有现代的IDE,很容易将文件折叠到你需要的东西,而且很多都有功能快捷列表。

3)使文件维护很痛苦。

4)我是小功能和重构的忠实粉丝。当你添加开销(创建一个新文件,将它添加到源代码控制,......)时,它鼓励人们编写更长的函数,而不是将1个函数分成3个部分,你只需要创建一个大的函数。

答案 8 :(得分:1)

如果您正在制作静态库,那么每个文件的一个功能具有技术优势(我猜这是Musl-libc项目之类的项目遵循此模式的原因之一。)< / p>

静态库与对象文件粒度相关联,因此如果您有一个由*组成的静态库libfoobar.a

 foo.o
     foo1
     foo2
 bar.o
     bar

然后,如果您链​​接bar函数的lib,bar.o存档成员将被链接但不会被foo.o成员链接。如果您链接foo1,那么foo.o成员将被关联,从而带来可能不必要的foo2功能。

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

  • 为了简单起见,我忽略了C ++名称错误