84

无论出于何种原因,我们公司都有一个编码指南,其中规定:

Each class shall have it's own header and implementation file.

因此,如果我们编写一个名为的类,MyString我们将需要关联的MyStringh.hMyString.cxx

还有其他人这样做吗?有没有人看到任何编译性能的影响?10000 个文件中的 5000 个类的编译速度是否与 2500 个文件中的 5000 个类一样快?如果不是,差异是否明显?

[我们编写 C++ 代码并使用 GCC 3.4.4 作为我们的日常编译器]

4

13 回答 13

86

这里的术语是翻译单元,您真的希望(如果可能)每个翻译单元有一个类,即每个 .cpp 文件一个类实现,并具有相应的同名 .h 文件。

以这种方式做事通常更有效(从编译/链接的角度来看),特别是如果您正在做诸如增量链接之类的事情。这个想法是,翻译单元是孤立的,这样,当一个翻译单元发生变化时,你不必重建很多东西,如果你开始将许多抽象集中到一个翻译单元中,你就必须这样做。

此外,您会发现许多错误/诊断是通过文件名报告的(“Myclass.cpp 中的错误,第 22 行”),如果文件和类之间存在一对一的对应关系,它会有所帮助。(或者我想您可以将其称为 2 对 1 对应)。

于 2008-08-26T14:34:39.797 回答
75

被数千行代码淹没?

目录中的每个类都有一组头文件/源文件似乎有点过头了。如果课程数量接近 100 或 1000,甚至可能会令人恐惧。

但是根据“让我们把所有东西放在一起”的理念与消息来源一起玩,结论是只有写文件的人才有希望不迷失在里面。即使使用 IDE,也很容易遗漏一些事情,因为当您使用 20,000 行代码的源代码时,您只会对任何与您的问题不完全相关的事情闭上思绪。

现实生活中的例子:那千行源代码中定义的类层次结构将自身封闭为菱形继承,并且子类中的某些方法被具有完全相同代码的方法覆盖。这很容易被忽略(谁想探索/检查 20,000 行的源代码?),并且当更改原始方法(错误更正)时,效果不如异常普遍。

依赖变得循环?

我在使用模板代码时遇到了这个问题,但我在常规 C++ 和 C 代码中看到了类似的问题。

将您的源代码分解为每个结构/类的 1 个标头可以让您:

  • 加速编译,因为您可以使用符号前向声明而不是包含整个对象
  • 类之间有循环依赖(§)(即类A有一个指向B的指针,B有一个指向A的指针)

在源代码控制的代码中,类依赖关系可能导致类在文件中上下移动,只是为了使头文件能够编译。在比较不同版本中的相同文件时,您不想研究此类移动的演变。

具有单独的标头使代码更加模块化,编译速度更快,并且更容易通过不同版本差异研究其演变

对于我的模板程序,我必须将头文件分成两个文件:包含模板类声明/定义的 .HPP 文件和包含上述类方法定义的 .INL 文件。

将所有这些代码放在一个且只有一个唯一的标头中意味着将类定义放在该文件的开头,将方法定义放在末尾。

然后,如果有人只需要一小部分代码,使用只有一个标头的解决方案,他们仍然需要为较慢的编译付费。

(§) 请注意,如果您知道哪个类拥有哪个类,则可以在类之间建立循环依赖关系。这是关于知道其他类存在的类的讨论,而不是 shared_ptr 循环依赖反模式。

最后一句话:标题应该是自给自足的

但是,必须通过多个标题和多个来源的解决方案来尊重一件事。

当您包含一个标头时,无论是哪个标头,您的源代码都必须干净地编译。

每个标题都应该是自给自足的。您应该开发代码,而不是通过 grep 您的 10,000 多个源文件项目来查找哪个标头定义了您需要包含的 1,000 行标头中的符号,只是因为一个枚举。

这意味着每个标头要么定义或前向声明它使用的所有符号,要么包括所有需要的标头(并且仅包含所需的标头)。

关于循环依赖的问题

下划线-d问:

你能解释一下使用单独的头文件对循环依赖有什么影响吗?我认为不会。即使两个类都在同一个标​​头中完全声明,我们也可以轻松地创建循环依赖,只需在我们在另一个中声明它的句柄之前提前声明一个。其他一切似乎都很好,但是单独的标头促进循环依赖的想法似乎很遥远

underscore_d,11 月 13 日 23:20

假设您有 2 个类模板,A 和 B。

假设类 A (resp. B) 的定义有一个指向 B (resp. A) 的指针。假设 A 类(对应 B)的方法实际上调用了 B(对应 A)的方法。

在类的定义和它们的方法的实现中都有循环依赖。

如果 A 和 B 是普通类,并且 A 和 B 的方法在 .CPP 文件中,则没有问题:您将使用前向声明,每个类定义都有一个标头,那么每个 CPP 将包含两个 HPP。

但是因为你有模板,你实际上必须重现上面的模式,但只有标题。

这表示:

  1. 定义头 A.def.hpp 和 B.def.hpp
  2. 实现头 A.inl.hpp 和 B.inl.hpp
  3. 为方便起见,“天真”标题 A.hpp 和 B.hpp

每个标头将具有以下特征:

  1. 在 A.def.hpp (resp. B.def.hpp) 中,您有一个类 B (resp. A) 的前向声明,这将使您能够声明对该类的指针/引用
  2. A.inl.hpp (resp. B.inl.hpp) 将包括 A.def.hpp 和 B.def.hpp,这将使来自 A (resp. B) 的方法能够使用类 B (resp. A) .
  3. A.hpp (resp. B.hpp) 将直接包含 A.def.hpp 和 A.inl.hpp (resp. B.def.hpp 和 B.inl.hpp)
  4. 当然,所有标头都需要自给自足,并受标头守卫保护

天真的用户将包括 A.hpp 和/或 B.hpp,从而忽略整个混乱。

拥有这种组织意味着库编写者可以解决 A 和 B 之间的循环依赖关系,同时将两个类保存在单独的文件中,一旦您理解了该方案,就可以轻松导航。

请注意,这是一个边缘案例(两个模板相互认识)。我希望大多数代码不需要那个技巧。

于 2008-09-19T20:30:44.897 回答
12

我们在工作中这样做,如果类和文件具有相同的名称,则更容易找到东西。至于性能,你真的不应该在一个项目中拥有 5000 个类。如果这样做,可能需要进行一些重构。

也就是说,有些情况下我们在一个文件中有多个类。那时它只是文件主类的私有帮助器类。

于 2008-08-26T14:23:12.040 回答
10

+1 用于分离。我刚刚进入一个项目,其中一些类位于具有不同名称的文件中,或者与另一个类混为一谈,并且不可能以快速有效的方式找到它们。您可以在构建中投入更多资源 - 您无法弥补程序员损失的时间,因为(s)他找不到要编辑的正确文件。

于 2008-08-26T14:28:28.050 回答
9

除了简单地“更清晰”之外,将类分离到单独的文件中还可以让多个开发人员更容易避免互相踩踏。在提交对版本控制工具的更改时,合并将减少。

于 2008-08-26T14:34:40.467 回答
7

我工作过的大多数地方都遵循这种做法。实际上,我已经为 BAE(Aust.)编写了编码标准,以及为什么不只是在没有真正理由的情况下刻板刻画的原因。

关于你关于源文件的问题,编译的时间不是很多,而是首先能够找到相关代码片段的问题。不是每个人都在使用 IDE。并且知道您只查找 MyClass.h 和 MyClass.cpp 与在一堆文件上运行“grep MyClass *.(h|cpp)”然后过滤掉 #include MyClass.h 语句相比确实节省了时间......

请注意,对于大量源文件对编译时间的影响,有一些变通方法。有关有趣的讨论,请参阅 John Lakos 的大型 C++ 软件设计。

您可能还想阅读 Steve McConnell 的 Code Complete 以获取有关编码指南的精彩章节。实际上,这本书是一本很棒的书,我会定期回来阅读

于 2008-08-26T14:31:52.940 回答
3

这样做是常见的做法,尤其是能够将 .h 包含在需要它的文件中。当然性能会受到影响,但在它出现之前不要考虑这个问题:)。
最好从分离的文件开始,然后尝试合并常用的 .h 文件以提高性能,如果你真的需要的话。这一切都归结为文件之间的依赖关系,这对每个项目都是非常具体的。

于 2008-08-26T14:25:31.287 回答
3

正如其他人所说,最佳实践是从代码维护和可理解性的角度将每个类放在自己的翻译单元中。然而,在大型系统上,这有时是不可取的 - 请参阅Bruce Dawson 的这篇文章中题为“使这些源文件更大”的部分,以讨论权衡取舍。

于 2008-08-27T16:41:01.733 回答
2

我发现这些准则在涉及头文件时特别有用:http: //google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Header_Files

于 2010-12-01T22:29:34.330 回答
1

每个文件只有一个类是非常有帮助的,但是如果您通过包含所有单个 C++ 文件的批量构建文件进行构建,那么它可以加快编译速度,因为对于许多编译器而言,启动时间相对较长。

于 2008-09-18T05:12:13.187 回答
1

我很惊讶几乎每个人都赞成每个班级有一个文件。问题在于,在“重构”时代,可能很难使文件名和类名保持同步。每次更改类名时,您也必须更改文件名,这意味着您还必须在包含文件的所有位置进行更改。

我个人将相关的类分组到一个文件中,然后给这样的文件一个有意义的名称,即使类名更改也不必更改。拥有更少的文件还可以更轻松地滚动文件树。我在 Windows 上使用 Visual Studio,在 Linux 上使用 Eclipse CDT,两者都有快捷键可以直接进入类声明,因此查找类声明既简单又快捷。

话虽如此,我认为一旦一个项目完成,或者它的结构已经“固化”,并且名称更改变得很少,每个文件都有一个类可能是有意义的。我希望有一个工具可以提取类并将它们放在不同的 .h 和 .cpp 文件中。但我不认为这是必不可少的。

选择还取决于一个项目的类型。在我看来,这个问题不值得一个非黑即白的答案,因为任何一种选择都有优点和缺点。

于 2015-01-07T14:33:45.183 回答
1

两个字:奥卡姆剃刀。每个文件保留一个类,并将相应的标题保存在单独的文件中。如果您不这样做,例如为每个文件保留一个功能,那么您必须创建关于什么构成功能的各种规则。通过为每个文件保留一个类可以获得更多收益。而且,即使是像样的工具也可以处理大量文件。保持简单,我的朋友。

于 2019-03-24T01:05:00.410 回答
0

同样的规则在这里适用,但它指出了一些允许的例外情况,如下所示:

  • 继承树
  • 仅在非常有限的范围内使用的类
  • 一些实用程序只是简单地放在一个通用的“utils.h”中
于 2008-08-26T14:30:11.557 回答