头文件中有多个类,而每个类中只有一个头文件
无论出于何种原因,我们公司都有一个编码准则,其中指出:
每个类应具有自己的标头和实现文件。
因此,如果我们编写了一个名为" MyString"的类,则需要一个关联的MyStringh.h和MyString.cxx。
还有其他人这样做吗?结果有人看到过任何编译性能影响吗? 10000个文件中的5000个类的编译速度是否与2500个文件中的5000个类的编译速度一样快?如果没有,差异是否明显?
[我们编写C ++代码,并使用GCC 3.4.4作为我们的日常编译器]
解决方案
回答
我们在工作时会这样做,如果类和文件具有相同的名称,则很容易找到东西。至于性能,我们实际上不应在单个项目中拥有5000个类。如果这样做,则可能需要进行一些重构。
就是说,在某些情况下,我们在一个文件中有多个类。那时它只是文件主类的私有帮助器类。
回答
这样做是很常见的做法,尤其是能够在需要它的文件中包含.h。当然,性能会受到影响,但是请尽量不要考虑这个问题,直到它出现:)。
最好从分开的文件开始,然后尝试合并常用的.h,以提高性能(如果我们确实需要)。一切都归结为文件之间的依赖关系,这对于每个项目都是非常特定的。
回答
+1分隔。我刚进入一个项目,其中某些类位于不同名称的文件中,或者与另一类混在一起,因此无法快速有效地找到它们。我们可以在无法弥补程序员时间浪费的构建上投入更多资源,因为他找不到合适的文件进行编辑。
回答
相同的规则在这里适用,但是它在允许的地方记录了一些例外,就像这样:
- 继承树
- 仅在非常有限的范围内使用的类
- 某些实用程序只是放在通用的" utils.h"中
回答
我工作过的大多数地方都遵循这种做法。实际上,我已经为BAE(澳大利亚)编写了编码标准,以及为什么没有理由就只在石头上雕刻东西的原因。
关于我们关于源文件的问题,这不是花太多时间进行编译,而是更多的是能够首先找到相关代码段的问题。并非每个人都在使用IDE。并且知道只查找MyClass.h和MyClass.cpp确实比在多个文件上运行" grep MyClass *。(h | cpp)"然后过滤掉#include MyClass.h语句节省了时间。
请注意,有许多变通办法可以解决大量源文件对编译时间的影响。有关有趣的讨论,请参见John Lakos的《大规模C ++软件设计》。
我们可能还希望阅读Steve McConnell撰写的Code Complete,以获得有关编码准则的出色章节。实际上,这是一本很棒的书,我会经常回到
回答
这里的术语是翻译单元,我们真的希望(如果可能)每个翻译单元有一个类,即每个.cpp文件有一个类实现,并且具有相同名称的相应.h文件。
从这种角度(从编译/链接)的角度来看,这样做通常更有效(尤其是在执行诸如增量链接之类的操作时)。想法是,翻译单元是隔离的,这样,当一个翻译单元发生更改时,我们不必重新构建很多东西,就像我们开始将许多抽象集中到一个翻译单元中一样。
另外,我们还会发现许多错误/诊断信息是通过文件名报告的(" Myclass.cpp中的错误,第22行"),如果文件和类之间存在一一对应的关系,则将很有帮助。 (或者我想我们可以将其称为2对1对应关系)。
回答
除了简单地"清晰"之外,将类分成单独的文件还可以使多个开发人员更轻松地避免彼此踩踏。将更改提交到版本控制工具时,合并将更少。
回答
正如其他人所说,最佳实践是从代码维护和易懂性的角度将每个类放在自己的翻译单元中。但是,在大型系统上,这有时是不可取的,有关权衡的讨论,请参见Bruce Dawson在本文中标题为"使那些源文件更大"的部分。
回答
每个文件只有一个类是非常有帮助的,但是如果我们通过包含所有单个C ++文件的bulkbuild文件进行构建,则由于许多编译器的启动时间相对较长,因此编译速度更快。
回答
数千行代码不知所措?
在目录中每个类具有一组头文件/源文件似乎有点过头了。如果班级数量达到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 underscore_d, Nov 13 at 23:20
假设我们有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类的前向声明(resp.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之间的循环依赖关系,同时将两个类都保存在单独的文件中,一旦我们了解了该方案,就可以轻松浏览。
请注意,这是一个极端情况(两个模板彼此了解)。我希望大多数代码都不需要该技巧。
回答
我发现这些准则对于头文件特别有用:
http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Header_Files