【问题标题】:Is there a standard #include convention for C++?C++ 有标准的#include 约定吗?
【发布时间】:2010-10-16 00:28:31
【问题描述】:

这是一个相当基本的问题,但它一直困扰着我。

我的项目有一堆 .cpp(实现)和 .hpp(定义)文件。

我发现当我添加额外的类和更多的类间依赖关系时,我必须#include 其他头文件。一两个星期后,我在很多地方都使用了#include 指令。稍后,我将尝试删除一些#include,并发现一切仍然有效,因为一些 OTHER 包含的类也是#include 我刚刚删除的内容。

是否有一个简单易行的规则来添加#includes,可以从一开始就阻止这种丑陋的混乱发生?最佳做法是什么?

例如,我参与的项目中,实施 .cpp 文件仅包含相应的定义 .hpp 文件,没有其他内容。如果还有其他 .hpp 文件需要被实现 .cpp 使用,它们都被定义 .hpp 文件引用。

【问题讨论】:

    标签: c++ include


    【解决方案1】:

    一些最佳实践:

    • 每个 .cpp 或 .C 文件都包含它需要的所有头文件,并且不依赖于包含其他相关头文件的头文件
    • 每个 .hpp 或 .h 文件都包含其所有依赖项,并且不依赖包含的头文件,包括其他相关头文件
    • 每个标题都用:

      #ifndef HEADER_XXX_INCLUDED
      #define HEADER_XXX_INCLUDED
      ...
      #endif /* HEADER_XXX_INCLUDED */
      
    • 标题在循环中不包含彼此

    • 通常:有一个“项目范围的头文件”,如“config.h”或“.h”,任何 .cpp 或 .C 文件始终包含第一个。通常,这包含与平台相关的配置数据、项目范围的常量和宏等。

    这些不一定是“最佳实践”,而是我通常遵循的规则:

    • 项目特定的标头包含为#include "...",位于系统范围标头之前,包含为#include <...>
    • 项目特定的标题包含在字母顺序中,以确保在包含它们的顺序上没有意外的隐藏要求。由于每个标头都应包含其依赖项并且标头应防止多次包含,因此您应该能够以您希望的任何顺序包含它们。

    【讨论】:

    • 我也这样做了#endif //HEADER_XXX_INCLUDED,以便查看 endif 匹配的内容。
    • 有趣。这是否意味着 .cpp 文件将具有与对应的 .hpp 文件相同(可能更多)的#include?​​span>
    • 我被你的第二个子弹弄糊涂了。恕我直言,您应该始终在您的课程的 .h 文件中包含您的课程所依赖的标头。如果每个班级都遵循这种做法,那么您应该能够依靠他们来包含他们需要的任何内容。你提倡包括所有的依赖关系?
    • 我喜欢创建除了#include 一个头文件之外什么都不做的单元测试。编译测试时会迅速清除意外的依赖关系。
    • 如果您使用的是 Visual Studio 2005 或更高版本,并且可移植性不是问题,您可以替换 '#ifndef HEADER_XXX' / '#define HEADER_XXX' / '#endif // HEADER_XXX' 行用一个简单的 '#pragma once' 行,你的 #ifndef 行会去。
    【解决方案2】:

    仅使用所需的最少数量的包含。无用的包括减慢编译速度。

    此外,如果您只需要指向一个类,则不必包含标题。在这种情况下,您可以使用如下前向声明:

    class BogoFactory;
    

    编辑:只是为了说清楚。当我说最低数量时,我并不是指构建包含链,例如:

    a.h
    #include "b.h"
    
    b.h
    #include "c.h"
    

    如果 a.h 需要 c.h,当然需要将其包含在 a.h 中,以防止出现维护问题。

    【讨论】:

    • 通常情况下,除非我可以只使用一两个前向声明,否则如果文件使用头文件中定义的内容,我会包含该头文件,即使它已经包含在其中一个头文件中我已经包括在内了。这可以防止 Joel Coehoorn 指出的问题。
    • 是的,我意识到我的帖子可以这样解释,我当然不是这个意思。已编辑。
    • 上述情况的例外是当我包含系统标头时。然后,我将尽可能少地包含这些内容。 (即,如果系统标头 A 包含系统标头 B,并且我包含系统标头 A,那么我将不包含系统标头 B。)
    • @RobH:但是你不能总是依赖它。在某些系统上,某些系统头文件不会包含它们所依赖的东西,您可能需要重新排序头文件才能编译整个该死的东西!烦人!
    【解决方案3】:

    我总是使用最小耦合原则。如果当前文件确实需要它,我只包含一个文件;如果我可以使用前向声明而不是完整定义,我将使用它。我的 .cpp 文件顶部总是有一堆#include。

    Bar.h:

    class Foo;
    
    class Bar
    {
        Foo * m_foo;
    };
    

    Bar.cpp:

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

    【讨论】:

    • 我明白了。所以听起来你的 .h 文件通常只有很少(如果有的话)#includes。
    • 是的——通常是用于标准库容器,例如#include .
    • 在一个(中等复杂的)代码库中,我发现使用这种方法可以显着缩短重建时间。
    • 我其实最喜欢这个答案,因为它很简单。
    • Bar.cpp 应该在 Foo.h 之前包含 Bar.h 以确保 Bar.h 包含它需要编译的所有头文件。见google-styleguide.googlecode.com/svn/trunk/…
    【解决方案4】:

    基于 antti.huima 所说的:

    假设您有 A、B 和 C 类。A 依赖于(包含)B,A 和 B 都依赖于 C。有一天您发现您不再需要在 A 中包含 C,因为 B 做到了为您服务,因此您删除了 #include 声明。

    现在,如果您在未来某个时间更新 B 以不再使用 C,会发生什么情况?突然间,A 无缘无故地坏了。

    【讨论】:

      【解决方案5】:

      C/C++ 中使用的#include 模型存在几个问题,主要问题是它没有表达实际的依赖关系图。相反,它只是以特定顺序连接一堆定义,通常导致定义在每个源文件中以不同的顺序出现。

      通常,您需要了解软件的包含文件层次结构,就像了解数据结构一样;您必须知道从哪里包含哪些文件。阅读您的源代码,了解哪些文件在层次结构中处于较高位置,这样您就可以避免意外添加包含,以便“从任何地方”包含它。添加新包含时请仔细考虑:我真的需要在此处包含此内容吗? 当我这样做时会引入哪些其他文件?

      两个可以提供帮助的约定(除了已经提到的):

      • 一个 class== 一个源文件 + 一个头文件,名称一致。 A 类进入 A.cpp 和 A.h。代码模板和 sn-ps 可以很好地减少在单独文件中声明每个类所需的输入量。
      • 使用 Impl 模式避免在头文件中暴露内部成员。 impl 模式意味着将所有内部成员放在 .cpp 文件中定义的结构中,并且在类中只有一个带有前向声明的私有指针。这意味着头文件只需要包含其公共接口所需的那些头文件,其内部成员所需的任何定义都将被排除在头文件之外。

      【讨论】:

        【解决方案6】:

        在 A.cpp 中,始终首先包含 A.h,以确保 A.h 没有额外的依赖项。 在所有系统文件之前的所有项目文件之前包含所有本地(相同模块)文件,再次确保不依赖于预包含的系统文件。 尽可能使用前向声明。 使用#indef/#define/#endif 模式 如果 A.h 中包含标头,则无需将其包含在 A.cpp 中。必须明确包含 A.cpp 需要的任何其他标头,即使它们碰巧由其他 .h 文件提供。

        【讨论】:

          【解决方案7】:
          【解决方案8】:

          查看 John Lakos 的大型 C++ 软件设计。以下是我遵循的(作为示例编写):

          界面

          // foo.h
          // 1) standard include guards.  DO NOT prefix with underscores.
          #ifndef PROJECT_FOO_H
          #define PROJECT_FOO_H
          
          // 2) include all dependencies necessary for compilation
          #include <vector>
          
          // 3) prefer forward declaration to #include
          class Bar;
          class Baz;
          #include <iosfwd> // this STL way to forward-declare istream, ostream
          
          class Foo { ... };
          #endif
          

          实施

          // foo.cxx
          // 1) precompiled header, if your build environment supports it
          #include "stdafx.h"
          
          // 2) always include your own header file first
          #include "foo.h"
          
          // 3) include other project-local dependencies
          #include "bar.h"
          #include "baz.h"
          
          // 4) include third-party dependencies
          #include <mysql.h>
          #include <dlfcn.h>
          #include <boost/lexical_cast.hpp>
          #include <iostream>
          

          预编译头文件

          // stdafx.h
          // 1) make this easy to disable, for testing
          #ifdef USE_PCH
          
          // 2) include all third-party dendencies.  Do not reference any project-local headers.
          #include <mysql.h>
          #include <dlfcn.h>
          #include <boost/lexical_cast.hpp>
          #include <iosfwd>
          #include <iostream>
          #include <vector>
          #endif
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-03-17
            • 2011-05-28
            • 2014-11-23
            • 1970-01-01
            • 2011-02-20
            相关资源
            最近更新 更多