【问题标题】:Is requiring a certain order for #includes in c++ a sign of bad library/header design?是否需要 C++ 中的#includes 的特定顺序是不良库/头文件设计的标志?
【发布时间】:2008-12-17 19:02:10
【问题描述】:

我使用过一些非常大规模的系统,但从未见过需要的订单,但最近遇到了。 STL 或 STD 库甚至 Boost 是否存在某些包含必须按特定顺序出现的情况?

【问题讨论】:

    标签: c++ include library-design


    【解决方案1】:

    STL 或 STD 库甚至 Boost 是否存在某些包含必须按特定顺序出现的情况?

    对于标准,答案很明确,。我想 Boost 也是如此,虽然我没有查过。

    来自 C 标准:

    标准标题可以按任何顺序包含;每一个都可以被多次包含在 一个给定的范围,与只包含一次没有任何不同,除了 包含<assert.h> 的效果取决于NDEBUG 的定义(参见7.2)。

    C++ 标准有类似的措辞。

    我的偏好是标题应该包含它们自己的依赖项,但我曾与认为这是“浪费”的人合作过。在我看来,不让 headers 包含它们的依赖项是一个毫无价值的早期优化。

    【讨论】:

    • 特别是因为很容易将保护条件放在标头上,以便它们只包含一次,从而使“优化”完全无用。
    • 同意。编译器可以优化掉额外的包含,如果你跳过它们,如果你包含的标题之一变成不包含你需要的东西,你会陷入混乱。除了前向声明,当它们适用时,就是这样。
    【解决方案2】:

    这听起来绝对是个糟糕的设计。如果以某种方式需要特定顺序,则库应提供 one 标头,其中包含 正确 顺序中的其他标头。

    就 boost 和 STL 而言,我很确定我还没有遇到过这种情况。

    【讨论】:

      【解决方案3】:

      需要以特定顺序指定包含几乎总是表明存在设计问题。减少无意中执行此操作的可能性的一种方法是练习将类的头文件作为第一个#include 包含在实现文件中。

      // A.cpp
      #include "A.h"
      #include "boost/shared_ptr.hpp"
      #include <vector>
      
      class A {
      // ...
      };
      

      这样,例如,如果 A.h 使用没有正确 #include 的向量,A.cpp 将无法编译。

      我不记得我在哪里捡到这个了;它可能来自 Lakos 的“Large Scale C++ Design”(一本真正可以使用更新的好书)。

      【讨论】:

        【解决方案4】:

        STL 或 STD 库甚至 Boost 是否存在某些包含必须按特定顺序出现的情况?

        我从来没有遇到过这种情况,如果是这样,那么必须尽快通知作者。哦,是的,这是一个非常糟糕的设计。

        【讨论】:

        • +1:难闻的气味。还有一个标准的 #ifdef 三明治,以确保所有 .h 都包含其所有正确的依赖项。
        【解决方案5】:

        将项目级兼容性标头(比如 compat.h)作为任何 .c/.cpp 源文件的 first 标头包含在内是一种常用技术,它定义了一堆必需的宏,例如 __STDC_LIMIT_MACROS , __REENTRANT 和其他项目范围的宏来影响标准头文件的后续行为。

        我很久以前第一次看到这种用法是由一位称职的程序员在内部库中使用的。后来我看到 'git'(臭名昭著的 dvcs)项目也使用了这种技术。

        【讨论】:

        • 我同意这种类型的配置设置可能是需要将标头包含在特定顺序中的要求的一个领域。另一次是当你使用预编译头文件时(但我不是预编译头文件的忠实粉丝)。
        • 我以前做过,但在这一点上,我真的更喜欢这些宏由我的构建参数 (g++ -D__STDC_LIMIT_MACROS ...) 定义,而不是作为源本身的一部分。不过,各有各的。
        • 好吧,在构建 args 中没有明确的方式来表达某些条件(平台、编译器)(这使得构建系统更加丑陋)
        【解决方案6】:

        那是“坏事”。已经提到了更好的方法;但我会详细说明。

        //a.h
        #ifndef _A_H_
        #define _A_H_
        
        //... code ...
        
        #endif
        // -----------------
        //b.h
        #ifndef _B_H_
        #define _B_H_
        #include a.h
        
        //... code ...
        
        #endif
        // -----------------
        //main.cpp Try 1
        #include "b.h" //<- okay!  b includes a, then does b
        // -----------------
        //main.cpp Try 2
        #include "a.h" //<- includes a
        #include "b.h" //<- okay!  b includes a, but skips redefining it, then does b
        // -----------------
        //main.cpp Try 3
        #include "b.h" //<- b includes a, then does b
        #include "a.h" //<- okay!  a skips redefining itself!
        // -----------------
        //main.cpp Try 4
        #include "a.h" //<- fail!  b is not included anywhere =(
        

        【讨论】:

        • 嗯,我认为你搞砸了你的'a's和'b's。在第 5 行,在文件 a.h 中,您将包含 b.h。但在后面的 cmets 中,您说:b 包括 a。
        【解决方案7】:

        如果标题中包含的函数和/或类(例如,A.h)依赖于另一个标题(例如,B.h)中定义的函数和/或类,我更愿意将后者包含在第一个中,而不是强制用户第一个以特定顺序包含两者。

        是的:

        // A.h
        #pragma once
        // or the #ifndef trick
        #include "B.h"
        
        // A.cpp
        #include "A.h"
        

        没有:

        // A.h
        #pragma once
        // or the #ifndef trick
        //#include "B.h"
        
        // A.cpp
        #include "B.h"
        #include "A.h"
        

        【讨论】:

          【解决方案8】:

          我喜欢按字母顺序包含标题 - 可以轻松查看我已经完成的工作。

          如果一个库因为顺序错误而无法工作,那么它就被破坏了,应该被修复以便与顺序无关。

          【讨论】:

            【解决方案9】:

            如果我没记错的话,对我来说这是一个糟糕的设计,不幸的是,它恰好位于带有 socket/socket2 的 win32 API 中。结果是包含顺序中的错误将触发一组错误,这些错误恰好来自无处可去,并且在依赖项更改定义但代码仍然可以编译的情况下可能难以调试。

            在任何其他情况下,您仍然会遇到麻烦。如果您不包含标头 x.h 因为 y.h 已经包含它,那么您的代码依赖于 y.h 对 x.h 的依赖。如果稍后 y.h 被重构并且不再需要 y.h,则删除包含将破坏您的代码库。这是耦合的标志(即使不是在类级别):代码库的一部分中的更改需要传播并扩展到代码的其他部分。

            【讨论】:

            • windows.h 和 gl.h 有同样的问题,很遗憾
            【解决方案10】:

            这可能表明您正在使用 MFC,而这反过来又可能表明设计不佳(开个玩笑……或者是吗?)

            (至少,我上次看MFC时,对你在哪里包含&lt;windows.h&gt;真的很挑剔)

            【讨论】:

              【解决方案11】:

              据我所知。这是非常糟糕的做法。不过,我最近在我的代码中遇到了一个 Windows 标头和一些奇怪的界面。

              【讨论】:

                【解决方案12】:

                您应该使用包含保护和前向声明,这样您就不应该对包含标头的顺序有太大问题。

                有时仍然需要首先或最后包含标题,不知道为什么。
                (例如:在 Source SDK 中)

                【讨论】:

                  【解决方案13】:

                  是的,在 c++ 中要求包含特定顺序是不良库/头文件设计的标志。

                  尽管前向声明可能需要包含多个文件才能完全使用一个类。请参见下面的示例:

                  //啊.h

                  class B; // forward declaration
                  
                  class A
                  {
                      void doStuff(const B& b);
                  };
                  

                  // main.cpp

                  #include <A.h>
                  #include <B.h>
                  
                  int main()
                  {
                      A a;
                      B b;
                      a.doStuff(b);
                  }
                  

                  【讨论】:

                  • 这很好,因为 main.cpp 中包含的顺序可以颠倒,没有问题。如果 A.h 不使用前向 decl 并且不包含 B.h 将是一个问题。这将需要 main.cpp 在包含 A.h 之前包含 B.h
                  猜你喜欢
                  • 2015-04-17
                  • 2010-10-19
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-06-12
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-09-11
                  • 1970-01-01
                  相关资源
                  最近更新 更多