我有一个用C ++构建的静态库。我把它分成了许多头文件和源文件。我想知道是否最好在一个头文件中包含库的客户端可能需要的所有头文件,而这些头文件又可以包含在他们的源代码中,或者只包含他们需要的头文件?这会导致代码不必要膨胀吗?我不确定那些未被使用的类或函数是否仍会被编译到他们的产品中。
感谢您的帮助。
答案 0 :(得分:3)
请记住,您编译的每个源文件都涉及编译器的独立调用。每次调用时,编译器都必须读入每个包含的头文件,解析它,并构建一个符号表。
当您在许多源文件中使用其中一个“包含世界”头文件时,它会显着影响您的构建时间。
有办法缓解这种情况;例如,Microsoft有一个预编译的头部功能,它基本上保存了符号表,供后续编译使用。
但还有另一个考虑因素。如果我要使用你的WhizzoString类,我不应该为SOAP,OpenGL以及你拥有的东西安装头文件。事实上,我宁愿WhizzoString.h只包含作为公共接口一部分的类型和符号的标题(即我作为你的用户需要的东西类)。
尽可能地,你应该尝试将包含从WhizzoString.h转移到WhizzoString.cpp:
确定:强>
// Only include the stuff needed for this class
#include "foo.h" // Foo class
#include "bar.h" // Bar class
public class WhizzoString
{
private Foo m_Foo;
private Bar * m_pBar;
.
.
.
}
<强>更好:强>
// Only include the stuff needed by the users of this class
#include "foo.h" // Foo class
class Bar; // Forward declaration
public class WhizzoString
{
private Foo m_Foo;
private Bar * m_pBar;
.
.
.
}
如果您的类的用户永远不必创建或使用Bar类型,并且该类不包含任何Bar实例,那么在头文件(WhizzoString)中仅提供Bar的前向声明可能就足够了。 cpp将有#include "bar.h"
)。这意味着包括WhizzoString.h在内的任何人都可以避免包含Bar.h及其包含的所有内容。
答案 1 :(得分:2)
如何给出两种选择:
#include <library.hpp> // include everything
#include <library/module.hpp> // only single module
这样你就没有一个巨大的包含文件,而对于你的单独文件,它们整齐地堆叠在一个目录中
答案 2 :(得分:2)
这取决于库,以及你如何构建它。请记住,库的头文件以及哪些片段在哪个头文件中,基本上是库的API的一部分。因此,如果您带领您的客户仔细挑选您的标题,那么您需要长时间支持该布局。如果API的某些部分真的是可选的并且很大,那么库通过一个文件或几个文件导出整个界面是相当普遍的。
考虑应该是编译时间:如果客户端必须包含二十几个文件来使用您的库,并且那些包含内部包含,那么如果经常使用,它可以显着增加大项目中的编译时间。如果你选择这条路线,请确保所有包含的内容不仅包含文件内容,还包括包含行。虽然注意:现代海湾合作委员会在这一特定问题上做得非常好,只需要围绕标题内容的警卫。
对于最终编译程序的膨胀,它取决于您的工具链,以及您如何编译库,而不是库的客户端如何包含头文件。 (需要注意的是,如果在头文件中声明静态数据对象,某些系统最终会在定义该数据的对象中进行链接,即使客户端不使用它。)
总之,除非它是一个非常大的库,或者是一个非常古老而又胡思乱想的工具链,否则我倾向于选择单一的包含。对我来说,将当前实现的划分冻结到库的API中是比其他人更大的担忧。
答案 3 :(得分:1)
通常,在链接最终可执行文件时,只会包含程序实际使用的符号和函数。您只需支付使用的费用。至少GCC工具链似乎对我有用。我无法代表所有工具链。
如果客户端总是必须包含相同的头文件集,那么为了方便起见,我认为提供包含其他头文件的“主”头文件是可以的。但要做到这一点,客户也可以选择只包含所需内容。
为减少大型项目的编译时间,通常的做法是尽可能少地包含标题以使单元编译。
答案 4 :(得分:1)
单个文件头的问题是专家编译器专家explained in detail by Dr. Dobbs。 切勿使用单个文件头!!! 每次在.cc / .cpp文件中包含头文件时,都必须重新编译头文件,因为您可以提供文件宏来更改已编译的头文件。因此,单个头文件将大大增加编译时间,而不会带来任何好处。使用C ++,您应该首先优化人工时间,而编译时间就是人工时间。绝对不要因为它会大大增加编译时间,而在任何标头中都包含了超出编译所需的内容,因此每个翻译单元(TU)都应该拥有自己的实现(.cc / .cpp)文件,并且每个TU都使用唯一的文件名命名;
在我十年的C ++ SDK开发经验中,我虔诚地总是在每个模块中都有三个文件。我有一个config.h
几乎包含在每个包含整个模块的先决条件(例如platform-config和stdint.h
)的头文件的 中。我还有一个global.h
文件,其中包含模块中的所有头文件。这个主要用于调试(提示 在global.h
文件中枚举接缝,以便进行更好的测试和调试代码)。此处遗漏的关键部分是,您应该确实有一个public.h
文件,该文件包含仅您的公共API。
在程序库中,例如boost及其丑陋的lower_snake_case类名,它们使用了半熟的最坏做法,即使用detail
(有时称为“ impl”)文件夹设计模式来“隐藏”他们的专用界面。为什么这是最糟糕的做法,背后有很长的背景,但总而言之,它会产生大量的 INSANE 冗余类型,从而将单行变成多行,并且不符合UML规范,它弄乱了UML依赖关系图,导致代码过于复杂,设计模式不一致,例如孩子实际上是父母,反之亦然。您不需要或不需要detail
文件夹,需要将public.h
头与一堆同级模块一起使用没有其他名称空间,其中detail
在一个与父母没有关系的兄弟姐妹而不是孩子。命名空间仅是一回事:将代码与其他人的代码接口,但是如果是您的代码,则可以控制它,并且应该使用唯一的类和功能名称,因为当您不使用名称空间时,这是不好的做法不需要这样做,因为它可能导致哈希表冲突,从而减慢编译过程。 UML是最好的实践,因此,如果您可以组织标头使其符合UML,那么根据定义,您的代码将更健壮和可移植。仅公开公共API只需一个public.h
文件;谢谢。