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

Fra*_*ank 10 c++ build-process file-organization compilation code-organization

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

原因是:

  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; ... }
Run Code Online (Sandbox Code Playgroud)

同样,优点是我可以只包含foo函数或只包含bar函数,整个项目的编译速度会更快,因为foobar.cpp一个文件,所以std::vector<int>(这只是其他一些昂贵的例子)编译模板化构造)必须只编译一次,而不是两次编译a foo.cppbar.cpp单独编译.当然,我上面的原因(3)对于这种情况是无效的:刚刚改变foo(){...}之后我必须重新编译整个可能很大的文件foobar.cpp.

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

Mr *_*ooz 9

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

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

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

如果我发现一个实现文件长达数千行,那通常表明那里存在太多,我需要将其分解.


dre*_*lax 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>
Run Code Online (Sandbox Code Playgroud)

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

  • 对我来说听起来很棒,再也不用查找函数的标题了...但是它们也可以有包含string.h中所有字符串函数的便捷标题,依此类推.另一方面,如果使用strlen,编译器可以做出包含"strlen.h"的假设(当然有警告) (4认同)

Ric*_*den 5

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

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

// getShapeColor.h
Color getShapeColor(Shape);

// getTextColor.h
Color getTextColor(Text);
Run Code Online (Sandbox Code Playgroud)

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

话虽这么说,即使在标准库的情况下,拆分各个函数也有一些潜在的好处。第一个是,当使用不安全版本的函数(例如strcpystrncpy )时,编译器可以生成有用的警告,其方式类似于 g++ 用于警告包含 <iostream.h> 与 <iostream> 的方式。

另一个优点是,当我想使用memmove时,我将不再因为包含内存而陷入困境!


PSk*_*cik 5

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

静态库与目标文件粒度链接,因此如果您有一个libfoobar.a由*组成的静态库:

 foo.o
     foo1
     foo2
 bar.o
     bar
Run Code Online (Sandbox Code Playgroud)

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

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

还有将少量相关函数/数据对象放入文件中的中间立场。这样,与 -ffunction-sections/-fdata-sections 相比,编译器可以更好地优化符号间引用,并且您仍然至少可以获得静态库的一些粒度。


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