C++ 头文件中的多个类与每个类的单个头文件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28160/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 12:20:39  来源:igfitidea点击:

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

c++performancefile-organization

提问by Mike Baker

For whatever reason, our company has a coding guideline that states:

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

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

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

So if we wrote a class called MyStringwe would need an associated MyStringh.hand MyString.cxx.

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

Does anyone else do this? Has anyone seen any compiling performance repercussions as a result? Does 5000 classes in 10000 files compile just as quickly as 5000 classes in 2500 files? If not, is the difference noticeable?

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

[We code C++ and use GCC 3.4.4 as our everyday compiler]

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

回答by

The term here is translation unitand you really want to (if possible) have one class per translation unit ie, one class implementation per .cpp file, with a corresponding .h file of the same name.

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

It's usually more efficient (from a compile/link) standpoint to do things this way, especially if you're doing things like incremental link and so forth. The idea being, translation units are isolated such that, when one translation unit changes, you don't have to rebuild a lot of stuff, as you would have to if you started lumping many abstractions into a single translation unit.

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

Also you'll find many errors/diagnostics are reported via file name ("Error in Myclass.cpp, line 22") and it helps if there's a one-to-one correspondence between files and classes. (Or I suppose you could call it a 2 to 1 correspondence).

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

回答by paercebal

Overwhelmed by thousands lines of code?

被数千行代码淹没?

Having one set of header/source files per class in a directory can seem overkill. And if the number of classes goes toward 100 or 1000, it can even be frightening.

在目录中为每个类设置一组头文件/源文件似乎有点过分。如果类的数量接近 100 或 1000,它甚至会令人恐惧。

But having played with sources following the philosophy "let's put together everything", the conclusion is that only the one who wrote the file has any hope to not be lost inside. Even with an IDE, it is easy to miss things because when you're playing with a source of 20,000 lines, you just close your mind for anything not exactly refering to your problem.

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

Real life example: the class hierarchy defined in those thousand lines sources closed itself into a diamond-inheritance, and some methods were overridden in child classes by methods with exactly the same code. This was easily overlooked (who wants to explore/check a 20,000 lines source code?), and when the original method was changed (bug correction), the effect was not as universal as excepted.

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

Dependancies becoming circular?

依赖变得循环?

I had this problem with templated code, but I saw similar problems with regular C++ and C code.

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

Breaking down your sources into 1 header per struct/class lets you:

将您的来源分解为每个结构/类的 1 个标头,您可以:

  • Speed up compilation because you can use symbol forward-declaration instead of including whole objects
  • Have circular dependencies between classes (§) (i.e. class A has a pointer to B, and B has a pointer to A)
  • 加速编译,因为您可以使用符号前向声明而不是包含整个对象
  • 类之间有循环依赖(§)(即类 A 有一个指向 B 的指针,而 B 有一个指向 A 的指针)

In source-controlled code, class dependencies could lead to regular moving of classes up and down the file, just to make the header compile. You don't want to study the evolution of such moves when comparing the same file in different versions.

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

Having separate headers makes the code more modular, faster to compile, and makes it easier to study its evolution through different versions diffs

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

For my template program, I had to divide my headers into two files: The .HPP file containing the template class declaration/definition, and the .INL file containing the definitions of the said class methods.

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

Putting all this code inside one and only one unique header would mean putting class definitions at the begining of this file, and the method definitions at the end.

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

And then, if someone needed only a small part of the code, with the one-header-only solution, they still would have to pay for the slower compilation.

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

(§) Note that you can have circular dependencies between classes if you know which class owns which. This is a discussion about classes having knowledge of the existence of other classes, not shared_ptr circular dependencies antipattern.

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

One last word: Headers should be self-sufficients

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

One thing, though, that must be respected by a solution of multiple headers and multiple sources.

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

When you include one header, no matter which header, your source must compile cleanly.

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

Each header should be self-sufficient. You're supposed to develop code, not treasure-hunting by greping your 10,000+ source files project to find which header defines the symbol in the 1,000 lines header you need to include just because of oneenum.

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

This means that either each header defines or forward-declare all the symbols it uses, or include all the needed headers (and only the needed headers).

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

Question about circular dependencies

关于循环依赖的问题

underscore-dasks:

下划线-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

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

underscore_d,11 月 13 日 23:20

Let's say you have 2 class templates, A and B.

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

Let's say the definition of class A (resp. B) has a pointer to B (resp. A). Let's also say the methods of class A (resp. B) actually call methods from B (resp. A).

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

You have a circular dependency both in the definition of the classes, and the implementations of their methods.

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

If A and B were normal classes, and A and B's methods were in .CPP files, there would be no problem: You would use a forward declaration, have a header for each class definitions, then each CPP would include both HPP.

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

But as you have templates, you actually have to reproduce that patterns above, but with headers only.

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

This means:

这意味着:

  1. a definition header A.def.hpp and B.def.hpp
  2. an implementation header A.inl.hpp and B.inl.hpp
  3. for convenience, a "naive" header A.hpp and B.hpp
  1. 定义头 A.def.hpp 和 B.def.hpp
  2. 一个实现头文件 A.inl.hpp 和 B.inl.hpp
  3. 为方便起见,一个“天真的”标题 A.hpp 和 B.hpp

Each header will have the following traits:

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

  1. In A.def.hpp (resp. B.def.hpp), you have a forward declaration of class B (resp. A), which will enable you to declare a pointer/reference to that class
  2. A.inl.hpp (resp. B.inl.hpp) will include both A.def.hpp and B.def.hpp, which will enable methods from A (resp. B) to use the class B (resp. A).
  3. A.hpp (resp. B.hpp) will directly include both A.def.hpp and A.inl.hpp (resp. B.def.hpp and B.inl.hpp)
  4. Of course, all headers need to be self sufficient, and protected by header guards
  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. 当然,所有的headers都需要自给自足,并且受到header guards的保护

The naive user will include A.hpp and/or B.hpp, thus ignoring the whole mess.

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

And having that organization means the library writer can solve the circular dependencies between A and B while keeping both classes in separate files, easy to navigate once you understand the scheme.

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

Please note that it was an edge case (two templates knowing each other). I expect most code to notneed that trick.

请注意,这是一个边缘情况(两个模板相互认识)。我希望大多数代码并不需要这种伎俩。

回答by Magnus Westin

We do that at work, its just easier to find stuff if the class and files have the same name. As for performance, you really shouldn't have 5000 classes in a single project. If you do, some refactoring might be in order.

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

That said, there are instances when we have multiple classes in one file. And that is when it's just a private helper class for the main class of the file.

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

回答by Chris Marasti-Georg

+1 for separation. I just came onto a project where some classes are in files with a different name, or lumped in with another class, and it is impossible to find these in a quick and efficient manner. You can throw more resources at a build - you can't make up lost programmer time because (s)he can't find the right file to edit.

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

回答by Matt Dillard

In addition to simply being "clearer", separating classes into separate files makes it easier for multiple developers not to step on each others toes. There will be less merging when it comes time to commit changes to your version control tool.

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

回答by Rob Wells

Most places where I have worked have folowed this practice. I've actually written coding standards for BAE (Aust.) along with the reasons why instead of just carving something in stone with no real justification.

我工作过的大多数地方都遵循这种做法。我实际上已经为 BAE (Aust.) 编写了编码标准以及为什么而不是仅仅在没有真正理由的情况下将某些东西刻在石头上。

Concerning your question about source files, it's not so much time to compile but more an issue of being able to find the relevant code snippet in the first place. Not everyone is using an IDE. And knowing that you just look for MyClass.h and MyClass.cpp really saves time compared to running "grep MyClass *.(h|cpp)" over a bunch of files and then filtering out the #include MyClass.h statements...

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

Mind you there are work-arounds for the impact of large numbers of source files on compile times. See Large Scale C++ Software Design by John Lakos for an interesting discussion.

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

You might also like to read Code Complete by Steve McConnell for an excellent chapter on coding guidelines. Actualy, this book is a great read that I keep coming back to regularly

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

回答by Brian Stewart

The best practice, as others have said, is to place each class in its own translation unit from a code maintenance and understandability perspective. However on large scale systems this is sometimes not advisable - see the section entitled "Make Those Source Files Bigger" in this articleby Bruce Dawson for a discussion of the tradeoffs.

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

回答by kokos

It's common practice to do this, especially to be able to include .h in the files that need it. Of course the performance is affected but try not to think about this problem until it arises :).
It's better to start with the files separated and after that try to merge the .h's that are commonly used together to improve performance if you really need to. It all comes down to dependencies between files and this is very specific to each project.

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

回答by Francois

I found these guidelines particularly useful when it comes to header files : http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Header_Files

我发现这些指南在头文件方面特别有用:http: //google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Header_Files

回答by Peter Ritter

I'm surprised that almost everyone is in favor of having one file per class. The problem with that is that in the age of 'refactoring' one may have a hard time keeping the file and class names in synch. Everytime you change a class name, you then have to change the file name too, which means that you have to also make a change everywhere the file is included.

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

I personally group related classes into a single files and then give such a file a meaningful name that won't have to change even if a class name changes. Having fewer files also makes scrolling through a file tree easier. I use Visual Studio on Windows and Eclipse CDT on Linux, and both have shortcut keys that take you straight to a class declaration, so finding a class declaration is easy and quick.

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

Having said that, I think once a project is completed, or its structure has 'solidified', and name changes become rare, it may make sense to have one class per file. I wish there was a tool that could extract classes and place them in distinct .h and .cpp files. But I don't see this as essential.

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

The choice also depends on the type of project one works on. In my opinion the issue doesn't deserve a black and white answer since either choice has pros and cons.

选择还取决于一个人所从事的项目类型。在我看来,这个问题不应该有一个非黑即白的答案,因为这两种选择都有利有弊。