无论出于何种原因,我们公司都有一个编码指南,规定:
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循环依赖反模式.
但有一件事必须通过多个标头和多个源的解决方案来尊重.
当您包含一个标头时,无论哪个标头,您的源都必须干净地编译.
每个标题应该是自给自足的.你应该开发代码,而不是通过greping你的10,000多个源文件项目来寻找代码,找到哪个标题定义了你需要包含的1000行标题中的符号只是因为一个枚举.
这意味着每个标头定义或前向声明它使用的所有符号,或者包括所有需要的标头(以及仅包含所需的标头).
下划线问:
你能解释一下使用单独的头文件对循环依赖关系有什么影响吗?我不这么认为.我们可以简单地创建循环依赖,即使两个类在同一个头中完全声明,只需在我们在另一个中声明一个句柄之前预先声明一个.其他所有东西似乎都很棒,但是单独的标题促进循环依赖的想法似乎很遥远
underscore_d,11月13日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(resp.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,以获得有关编码指南的精彩章节.实际上,这本书是一本很棒的读物,我会不断回头