关于C ++:头文件中有多个类,而每个类中只有一个头文件

关于C ++:头文件中有多个类,而每个类中只有一个头文件

Multiple classes in a header file vs. a single header file per class

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

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

因此,如果我们编写了一个名为MyString的类,则需要一个关联的MyStringh.h和MyString.cxx。

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

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


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

从这种方式(从编译/链接)的角度来看,这样做通常更有效(尤其是在执行诸如增量链接之类的操作时)。想法是,翻译单元是隔离的,这样,当一个翻译单元发生更改时,您不必重新构建很多东西,就像您开始将多个抽象元素合并到一个翻译单元中一样。

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


数千行代码不知所措?

在目录中每个类具有一组头文件/源文件似乎有点过头了。如果班级数量达到100或1000,甚至会令人恐惧。

但是,按照"让所有东西放在一起"的理念玩弄了源代码之后,得出的结论是,只有撰写文件的人才有希望不会丢失。即使使用IDE,也很容易遗漏任何东西,因为当您使用20,000行的源代码时,您会为任何与您的问题不完全相关的事情而闭嘴。

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

依赖关系变得循环了吗?

我在模板化代码中遇到了这个问题,但是在常规C ++和C代码中却遇到了类似的问题。

将源分成每个结构/类1个标头,您可以:

  • 加快编译速度,因为您可以使用符号前向声明而不是包括整个对象
  • 类(§)之间具有循环依赖关系(即,类A具有指向B的指针,而B具有指向A的指针)
  • 在源代码控制的代码中,类依赖关系可能导致类在文件中上下移动,以使标头得以编译。在比较不同版本的同一文件时,您不想研究此类移动的演变。

    具有单独的标头可以使代码更具模块化,编译速度更快,并且可以更轻松地通过不同版本的差异来研究其演变

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

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

    然后,如果某人只需要一小部分代码,并且仅使用一个标头的解决方案,他们仍然必须为较慢的编译付出代价。

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

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

    但是,必须通过多个标头和多个源的解决方案来尊重这一点。

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

    每个标题都应该是自给自足的。您应该开发代码,而不是通过捉住10,000+个源文件项目来寻宝,以发现哪个标头定义了仅由于一个枚举而需要包括的1,000行标头中的符号。

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

    关于循环依赖的问题

    下划线-d问:

    Can you explain how using separate headers makes any difference to circular dependencies? I don't think it does. We can trivially create a circular dependency even if both classes are fully declared in the same header, simply by forward-declaring one in advance before we declare a handle to it in the other. Everything else seems to be great points, but the idea that separate headers facilitate circular dependencies seems way off

    Ok.

    underscore_d, Nov 13 at 23:20

    Ok.

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

    假设类A(分别为B)的定义具有指向B(分别为A)的指针。我们还说A类(分别为B)的方法实际上是从B(分别为A)调用方法的。

    您在类的定义及其方法的实现中都具有循环依赖关系。

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

    但是,因为有了模板,实际上您必须在上面重现该模式,但仅使用标头。

    这意味着:

  • 定义头A.def.hpp和B.def.hpp
  • 实现标头A.inl.hpp和B.inl.hpp
  • 为了方便起见,"天真"标头A.hpp和B.hpp
  • 每个标头将具有以下特征:

  • 在A.def.hpp(分别为B.def.hpp)中,您具有B类(分别为A)的前向声明,这将使您能够声明对该类的指针/引用
  • A.inl.hpp(分别为B.inl.hpp)将同时包含A.def.hpp和B.def.hpp,这将使A(分别为B)的方法可以使用B类(分别为A) 。
  • A.hpp(分别为B.hpp)将直接包含A.def.hpp和A.inl.hpp(分别为B.def.hpp和B.inl.hpp)
  • 当然,所有标头都必须自给自足,并由标头防护装置保护
  • 天真的用户将包括A.hpp和/或B.hpp,从而忽略了整个混乱。

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

    请注意,这是一个极端情况(两个模板彼此了解)。我希望大多数代码都不需要该技巧。

    好。


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

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


    除了简单地"清晰"之外,将类划分为单独的文件还可以使多个开发人员更轻松地相互踩踏。将更改提交到版本控制工具时,合并将更少。


    +1分隔。我刚进入一个项目,其中某些类位于不同名称的文件中,或者与另一个类混在一起,因此无法快速有效地找到它们。您可以在构建时投入更多资源-您无法弥补程序员的时间浪费,因为他找不到合适的文件进行编辑。


    我工作过的大多数地方都遵循这种做法。我实际上已经编写了BAE(澳大利亚)的编码标准,以及为什么没有理由而没有在石头上雕刻东西的原因。

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

    请注意,有许多变通办法可以解决大量源文件对编译时间的影响。有关有趣的讨论,请参见John Lakos的《大规模C ++软件设计》。

    您可能还希望阅读Steve McConnell撰写的Code Complete,以获得有关编码准则的出色章节。实际上,这本书是一本不错的书,我会定期回到


    正如其他人所说,最佳实践是从代码维护和易懂性的角度将每个类放在自己的翻译单元中。但是,在大型系统上,这有时是不可取的-有关权衡的讨论,请参见Bruce Dawson在本文中标题为"使那些源文件更大"的部分。


    这样做是很常见的做法,尤其是能够在需要它的文件中包含.h。当然,性能会受到影响,但请尽量不要考虑这个问题,直到它出现:)。
    最好从分开的文件开始,然后尝试合并常用的.h,以提高性能(如果您确实需要)。归结为文件之间的依赖关系,这对于每个项目而言都是非常特定的。


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


    令我惊讶的是,几乎每个人都赞成每堂课一个文件。这样做的问题是,在"重构"时代,保持文件名和类名同步可能很困难。每次更改类名时,都必须也更改文件名,这意味着还必须在包含文件的所有位置进行更改。

    我个人将相关的类分组到一个文件中,然后为这样的文件提供一个有意义的名称,即使类名称发生更改也不必更改。更少的文件也使滚动浏览文件树变得更加容易。
    我在Windows上使用Visual Studio,在Linux上使用Eclipse CDT,它们都有快捷键,可以直接转到类声明,因此查找类声明既简单又快捷。

    话虽如此,我认为一旦项目完成或者其结构已经"巩固",并且名称更改变得罕见,那么每个文件只有一个类是有意义的。我希望有一个可以提取类并将其放置在不同的.h和.cpp文件中的工具。但是我认为这不是必不可少的。

    选择还取决于一个项目的类型。在我看来,这个问题不值得一black而就,因为这两种选择都有其优缺点。


    每个文件只有一个类是非常有帮助的,但是如果您通过包含所有单个C ++文件的bulkbuild文件进行构建,则由于许多编译器的启动时间相对较长,因此编译速度更快。


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


    同样的规则在这里适用,但是它在允许的地方记录了一些例外,就像这样:

    • 继承树
    • 仅在非常有限的范围内使用的类
    • 某些实用程序只是放在通用的" utils.h"中


    推荐阅读