Fra*_*ank 10 c++ build-process file-organization compilation code-organization
我喜欢组织我的代码,所以理想情况下我想要每个文件一个类,或者当我有非成员函数时,每个文件需要一个函数.
原因是:
当我阅读代码时,我将始终知道在哪个文件中我应该找到某个函数或类.
如果它是每个头文件的一个类或一个非成员函数,那么当我include成为头文件时,我不会包含整个混乱
.
如果我在函数中做了一些小改动,那么只需要重新编译该函数.
但是,将所有内容拆分为许多标头和许多实现文件都会使编译速度变慢.在我的项目中,大多数函数访问一定数量的模板化其他库函数.因此代码将反复编译,每个实现文件一次.编译我的整个项目目前在一台机器上需要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.cpp和bar.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>
Run Code Online (Sandbox Code Playgroud)
每个文件一个类是个好主意.
我们使用每个文件一个外部函数的原则。但是,在此文件中,未命名的命名空间中可能还有其他几个“帮助程序”函数,用于实现该函数。
根据我们的经验,与其他一些评论相反,这有两个主要好处。首先是构建时间更快,因为模块只需要在修改其特定 API 时重新构建。第二个优点是,通过使用通用命名方案,无需花费时间搜索包含您要调用的函数的标头:
// getShapeColor.h
Color getShapeColor(Shape);
// getTextColor.h
Color getTextColor(Text);
Run Code Online (Sandbox Code Playgroud)
我不同意标准库是每个文件不使用一个(外部)函数的一个很好的例子。标准库永远不会改变,并且具有定义良好的接口,因此上述两点都不适用于它们。
话虽这么说,即使在标准库的情况下,拆分各个函数也有一些潜在的好处。第一个是,当使用不安全版本的函数(例如strcpy与strncpy )时,编译器可以生成有用的警告,其方式类似于 g++ 用于警告包含 <iostream.h> 与 <iostream> 的方式。
另一个优点是,当我想使用memmove时,我将不再因为包含内存而陷入困境!
如果您正在制作静态库,每个文件一个函数具有技术优势(我想这就是像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 相比,编译器可以更好地优化符号间引用,并且您仍然至少可以获得静态库的一些粒度。