【问题标题】:C++ Pre-compiled header and included file organizationC++ 预编译的头文件和包含的文件组织
【发布时间】:2014-11-13 03:59:13
【问题描述】:

我有一个非常大的 Native C++ 项目,其中有数百个类,每个类都定义在自己的 .cpp 和 .h 文件中。这里没有什么奇怪的。我在预编译的头文件 stdafx.h 文件中声明了所有类,并且还包含在 stdafx.h 文件中,如下所示(以允许 Foo 引用 Bar 和 Bar 引用 Foo)。

这个组织是不好的做法吗?

基本上,所有类都被声明并包含在每个对象中。我假设编译器不会通过包含编译特定 .cpp 目标文件所不需要的东西来生成开销二进制文件。到目前为止,我的编译时间也没有任何问题。我只是想知道这是否是一个糟糕的代码组织。

我喜欢这种布局,因为 stdafx.h 文件本质上是一个代码映射,因为所有类都在此处声明,并且它表明嵌套命名空间是一种易于阅读的格式。这也意味着我的单个类 .h 文件不需要多个包含语句,这意味着我在更改文件名或目录时需要维护的代码更少。

stdafx.h:

#pragma once
namespace MyProject
{
    class Foo;
    class Bar;
}

#include "Foo.h"
#include "Bar.h"

foo.h:

#pragma once

namespace MyProject
{
    class Foo
    {
        // Declare class members, methods, etc
    }
}

foo.cpp:

#include "stdafx.h"

namespace MyProject
{
    class Foo
    {
        // Define class members, methods, etc
    }
}

【问题讨论】:

  • 不,它只是无法编译。您是否忘记了包含守卫?
  • 但是现在你必须在每次添加或删除一个类时更新stdafx.h(这意味着重新编译整个项目),并且至少你的一些类不会在上下文之外编译一个项目的 stdafx,因为他们没有转发声明他们需要的东西。
  • 啊在每个标题上添加一个#pragma once
  • 是的,组织不好。预编译头文件用于加快编译时间。您至少必须在餐巾纸的背面计算标题很少更改并且只包括那些。只需将所有内容都包含在预编译的标头中,您就可以获得完全 0 的性能。
  • 困扰我的是stdafx.h包含Foo.h,而Foo.h包含stdafx.h。这对我来说看起来不太好。 (据我了解,stdafx.h 应该包含在其他标题中;只能包含在源文件中。)

标签: c++ c++11 visual-studio-2013 native


【解决方案1】:

在我看来,这里有 4 个关键因素:

  1. 编译时间:请记住,#include "x.h" 在该行嵌入了 x.h 中的所有代码。如果项目要大幅增长,请注意您将如何影响未来的编译时间。
  2. FooBar 稳定性:如果 FooBar 发生变化,您将不得不继续重新编译 stdafx.h
  3. 循环定义:您将无法再使用前向声明来破坏循环定义。
  4. 显式依赖项:您将无法查看包含内容并知道特定文件中使用了哪些内容。

如果你觉得你的优点超过了这里列出的缺点,那么你所做的事情并没有违法,那就去做吧!

【讨论】:

  • 为了澄清,外部依赖项包含在单独的 .cpp 文件中,以跟踪每个类中包含的内容。只有内部包含或始终使用的包含在预编译头中。
  • 1.在这一点上,编译时间不是问题。 stdafx.cpp 大约需要 5 秒,其余的 .cpp 文件总共需要大约 5 秒。这里没问题,因为我没有包括很多外部。 2. 如前所述,我目前不关心必须重新编译stdafx.h,因为它很快。 3. 循环定义是必须的。有没有更好的方法来做我正在做的事情? 4. 每个 .cpp 文件中都包含外部依赖项。
  • @RussellTrahan On 2 我同意AndyG 的评论,那为什么还要预编译头文件呢?此时您可以将/FI 与所有标题一起使用,并消除一些复杂性。至于 3 这意味着您的包含必须进入您的实现文件,而不是您的标题。如果您的所有标头都在 stdafx.h 中,您将无法做到这一点。有关更多信息,请参阅:en.wikipedia.org/wiki/…
  • 我同意我可能正在利用预编译的头文件,但事实是我的许多更改都在定义中,而不是在声明中。在我的大多数重新编译事件中,我没有重新编译 stdafx.h。我可以使用我不知道的强制包含功能,但我不确定这会给我带来什么。另外,我认为所有评论者都没有意识到循环引用确实像我写的那样工作(Foo CAN 参考栏)。
  • 无论如何,我将其标记为答案,因为我收集到的是这里的代码可能是非正统的,但在运行时不会招致任何惩罚,这是我在我的原始问题。就个人而言,我认为像这样将单个文件布置为代码映射比编译时间可能但不太可能发生的变化更有益(基于我编辑这个项目的方式)。
【解决方案2】:
  1. 标题应该包含它需要的所有内容,不多也不少。
    这是易用性和简单封装的基本要求。

    你的例子明确地失败了这一点。

    要检测违规行为,请始终在翻译单元中首先包含相应的标头。
    如果您使用预编译的头文件,这会稍作修改,在这种情况下,必须首先包含它们。

  2. 预编译的标头应该只包含很少变化的元素。

    对预编译头或其依赖项的任何更改都将强制重新编译所有内容,这与使用一个完全相反。

    你的例子也没有做到这一点。

总之,还有很多需要改进的地方。

【讨论】:

  • 因此,一个简短的 stdafx.h 文件真的比消除分散在我的项目中的数百个#include 语句更重要。我认为这消除了大部分内部#include 引用。编译 stdafx.cpp 大约需要 5 秒——没问题。
  • 如果你这样做,为什么不把所有东西都包含在一个大的“monster.cpp”(头文件和源文件)中,然后编译它。如果重新编译一切都不是问题,那么不应该太长......重要的是不要让它变短,但要确保它(包括依赖项)很少更改,因为这会强制重新编译所有内容。
  • @RussellTrahan:你可能想看看这里:减少依赖stackoverflow.com/q/23091601
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-15
  • 1970-01-01
  • 1970-01-01
  • 2021-08-09
  • 1970-01-01
  • 2011-06-13
相关资源
最近更新 更多