【问题标题】:C++ include header conventionsC++ 包含头文件约定
【发布时间】:2010-03-31 17:43:13
【问题描述】:

假设我有一个文件 X.h,它定义了一个类 X,其方法在 X.cc 中实现。 X.h 文件中包含了 Y.h 文件,因为它需要 Y 来定义 X 类。在 X.cc 中,我们可以参考 到 Y,因为 X.h 已经包含了 Y.h。我还应该在 X.cc 中包含 Y.h 吗?

我知道我不需要,我可以依靠标头保护来防止多重包含。 但一方面,包括 Y.h 使得 X.cc 更加独立于 X.h (不能 当然完全独立)。公认的做法是什么?

另一个例子:在 .h 和 .cc 文件中都包含 <iostream>。我看到有些人这样做 有些没有。

【问题讨论】:

    标签: c++ include header-files


    【解决方案1】:

    尽量少。在标题中,更喜欢前向声明而不是完整定义。例如,使用iosfwd 而不是ostream

    也就是说,X.h 和 X.cc 代表相同的逻辑单元。如果您对 Y.h 的依赖发生了变化(例如,将其转换为前向声明),那么您无论如何都会更改类。所以你可以合理地将#include "Y.h" 移动到 X.cc。

    换句话说,X.cc 和 X.h 齐头并进。 X.cc 可以可靠地假设 X.h 中的内容。因此,如果 X.h 包含,则无需重新包含某些内容。

    您“无论如何都包含它”的依赖关系发生在资源其他而不是您自己的资源中。例如,如果您需要 Z.h,即使 Y.h 需要,您也会包含它。 X.h 无法可靠地假设 Y.h 的内容,因为 X.h 不与 Y.h 一起使用,而是使用它。

    【讨论】:

      【解决方案2】:

      我建议在 X.cc 中包含 Y 的标头包含,即使它看起来是多余的。它为您提供了对依赖项非常明确的优势。

      作为相关说明,您应该始终将 cpp 文件的相关标头#i​​nclude 作为第一个 #include'd 文件。 (X.cpp 中的第一个包含应该是 X.h)这保证了标头包含正确的文件来解决其自身的依赖关系,否则您可能会无意中依赖源文件中包含的顺序。

      【讨论】:

      • 这对我来说似乎很奇怪,因为您必须遍历头文件链才能完全确定依赖关系,因此 .cc 文件中的包含列表无论如何只是部分的.既然如此,为什么还要双重包含?
      【解决方案3】:

      我不确定为什么这样做是个好习惯。

      另一方面,X.h 中包含不必要的文件是我认为非常好的做法。

      例如,在以下场景中:

      X.h

      #include "Y.h"
      
      class X
      {
      private:
          Y * m_pY;
      
      public:
          X();
          ~X();
      }
      

      转发声明Y 就足够了。 X 类的客户端无需承担包含 Y 的头文件的费用:

      X.h

      class Y; // include Y.h in X.cc instead
      
      class X
      {
      private:
          Y * m_pY;
      
      public:
          X();
          ~X();
      }
      

      这在头文件中是可能的,因为类X 的声明不需要关于Y 的具体细节(例如实例的大小);只有Y 是一个类。此外,X 类的客户端从不处理 Y 类型,因为它不是 X 的公共接口的一部分。

      对于大型项目,避免头文件中不必要的包含指令可以显着缩短构建时间。

      它还可以避免使用您的私有/实现类中的符号污染您的类用户的命名空间。

      【讨论】:

        【解决方案4】:

        我认为你应该这样做。随着代码的增长和一些实现的变化,一些包含我的头文件被删除,该头文件包含在另一个头文件中,而另一个头文件包含在某个头文件中......您(或其他人)所做的更改不再有效。

        【讨论】:

        • 嗯?添加多余的#include 指令对这有什么帮助?
        • @Beta:因为如果你在 .cc 中有 #include,没有人会从那里拉出它,因为 .h 不再需要那个 #include... 所以 .cc 仍然可以它需要包含的 .h。
        • @SamB: 1) 当你从 A.hh 中删除 "#include X" 并且 A.cc 仍然需要它时,你将它添加到 A.cc;如果您忘记了,编译器会立即提醒您。预先添加它并不比在需要时添加它更容易。 2) FRotthowe 正在谈论破坏 B,因为 B 需要 X 并通过 #include A.hh 获取它(可能通过头文件链),并且在 A.cc 中保留冗余的 #include 对 没有任何作用防止这种情况发生。
        • 啊,对不起。我猜我的问题错了。对所有的 X 和 Y 感到困惑......好吧,也许首先包含不应该在标题中。前向声明应该具有相同的效果。
        【解决方案5】:

        这并不重要[至少,除非你有一个巨大的项目],但我可能倾向于包括它。

        【讨论】:

        • 这绝对很重要。标题中的#includes 会增加构建时间。
        【解决方案6】:

        从您的示例中,我只会在 X.cc 中包含 X.h 只是为了减少包含的数量。是的,在更一般的情况下,您的 A.cc 包括 X.h,并且作为能够引用 Y.h 中的内容的副作用,当您可以删除 X.h 时,您会发现自己必须手动添加 Y.h,因为您之前没有添加它.至少编译器会根据你的情况提醒你。

        对于<iostream>,无论是在头文件还是模块中,您都需要它。

        【讨论】:

          【解决方案7】:

          头文件中的#include 语句会增加编译时间。对于像您描述的只有一个依赖项的玩具应用程序或大型系统,这不是问题。但是随着应用程序的增长,编译时间也会增长。 MSVC 下的一个解决方案是使用预编译的标头(这会带来一系列令人头疼的问题),或者消除标头中的所有 #include 语句。后者是我的默认方法,我首先尝试在需要时使用前向声明:

          y.h :

          class Y {public: int foo_; };
          

          x.h :

          class Y;
          class X { public: Y* y_; };
          

          main.cc:

          #include "y.h"
          #include "x.h"
          
          int main()
          {
            X x;
            return 0;
          }
          

          如果无法使用前向声明,请记住 #include 只是将指定文件的内容带到该点,这可以从 cc 文件和从头文件中轻松完成:

          y.h :

          class Y {public: int foo_; };
          

          x.h :

          class X {public: Y y_; }; // note this declaration requires a concrete type for Y
          

          main.cc:

          #include "y.h"
          #include "x.h"
          
          int main()
          {
            X x;
            return 0;
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-01-28
            • 1970-01-01
            • 2012-01-21
            • 1970-01-01
            相关资源
            最近更新 更多