【问题标题】:Is there any situation where you wouldn't want include guards?有没有什么情况你不想包括警卫?
【发布时间】:2011-07-08 16:02:31
【问题描述】:

我知道为什么存在包含守卫,并且 #pragma once 不是标准的,因此并非所有编译器等都支持。

我的问题是不同的:

有什么合理的理由不拥有它们吗?我还没有遇到过这样一种情况,理论上,不在一个文件中提供包含警卫,该文件应该包含在其他地方。有没有人举个例子说明没有它们有实际好处?

我问的原因 - 对我来说,它们似乎很多余,因为你总是使用它们,而且 #pragma once 的行为也可以自动应用于几乎所有内容。

【问题讨论】:

    标签: c++ include-guards


    【解决方案1】:

    我见过根据包含之前定义的宏生成代码的标头。在这种情况下,有时需要将这些宏定义为一个(一组)值,包含标题,重新定义宏,然后再次包含。
    看到这种情况的每个人都同意它是丑陋的,最好避免,但有时(例如,如果所述标头中的代码是通过其他方式生成的)这样做的危害较小。

    除此之外,我想不出理由。

    【讨论】:

    • 我在 FFTW 库中遇到过这种特殊用途(这是不久前的,也许现在已经改变了)。定义了许多函数,以便可以为不同的底层类型创建它们:in、double、float 等,因此您可以根据需要定义任意数量的类型并重新包含文件。但是,这是一个 C 库。在 C++ 中,我们当然会使用模板...
    • @the_mandrill:在 C++ 中,这实际上用于模板不适合问题的某些情况。特别是使用 boost 预处理器库自动生成具有不同数量参数的相同代码,如在 boost::bind 中使用 0、1、2...N 个参数。
    • @David Rodríguez:据我了解,boost::bind 是通过为最多 9 个参数提供适当的模板来实现的。编辑:好的,我找到了一个地方,boost 这样做是为了避免 return x 使用模板参数作为返回值的函数的情况(当返回类型为 void 时),但我总是设法通过特定的模板专业化来解决这些问题类型void。他们在没有包含守卫的情况下走这条路,但没有必要重新定义 return 宏来解决这个问题。
    • @Mephane:我的错,我没有检查它是什么库,并且误认为是另一个库。 boost::signal 有一个 signal_template.hpp 标头,它包含在 signal#.hpp 中,其中 # 范围从 0 到 10。然后 signal.hpp 包含所有 signal#.hpp,因此 signal_template.hpp 在 signal 中包含 11 次.hpp
    • 我在嵌入式平台上使用了多包含构造,在这些平台上,通过指向结构的指针访问结构成员的效率要低得多(超过 2 倍的成本)访问全局变量。拥有对相同结构类型的单独全局变量进行操作的同一段代码的两个副本比拥有一个接受结构指针作为参数的副本便宜。这种设计在其他一些平台上效率不高,但与更传统的替代方法相比,它可以节省大量空间,甚至可以节省更多的执行时间。
    【解决方案2】:

    @sbi 已经谈到了代码生成,所以让我举个例子。

    假设您有很多项目的枚举,并且您想为其每个元素生成一堆函数...

    一种解决方案是使用这种多重包含技巧。

    // myenumeration.td
    MY_ENUMERATION_META_FUNCTION(Item1)
    MY_ENUMERATION_META_FUNCTION(Item2)
    MY_ENUMERATION_META_FUNCTION(Item3)
    MY_ENUMERATION_META_FUNCTION(Item4)
    MY_ENUMERATION_META_FUNCTION(Item5)
    

    然后人们就这样使用它:

    #define MY_ENUMERATION_META_FUNCTION(Item_) \
      case Item_: return #Item_;
    
    char const* print(MyEnum i)
    {
      switch(i) {
        #include "myenumeration.td"
      }
    
      __unreachable__("print");
      return 0; // to shut up gcc
    }
    
    #undef MY_ENUMERATION_META_FUNCTION
    

    这是好是坏取决于您,但显然每次向枚举中添加新值时不必遍历所有实用程序函数很有用。

    【讨论】:

    • 如果您将#define MY_ENUMERATION_META_FUNCTION 放在 myenumeration.td 的顶部,您可以只包含第二个文件 once 并且可以包含第一个文件一次。您的示例看起来像可以轻松避免的任意循环包含依赖项。如果我错了,请纠正我。
    • @Mephane:鉴于“enumeration.td”不包含任何内容,我不明白如何存在循环依赖...此外,宏 MY_ENUMERATION_META_FUNCTION 也不打算在枚举文件,因为它会阻止它用作元函数,所以它会有点违背在那里定义它的目的。
    • 啊,现在我明白了。第二个文件不是另一个包含要重用代码的头文件,而是一个实际的实现,每个实现都可能定义自己的MY_ENUMERATION_META_FUNCTION 版本。是的,它完全感觉像是一个黑客。当然,能够做这样的事情是否是一种实际的好处是相当主观的,但我现在看到了这种可能性。
    • @Mephane:我必须承认我对此不太满意。它在 LLVM/Clang 代码库中大量使用,并且具有不需要维护脚本(用另一种语言)来生成这些函数的优点。正确的面向对象的工厂和 al 可以工作......但会慢一些,而且 LLVM/Clang 在内存和速度方面都经过高度优化,所以他们不介意使用 hack,只要它对他们有利。
    【解决方案3】:
    <cassert>
    <assert.h>
    

    "assert 宏每次都根据 NDEBUG 的当前状态重新定义 包含在内。”

    【讨论】:

      【解决方案4】:

      如果您在项目中有两个使用相同包含保护的标头,则可能会出现问题,例如如果您有两个第三方库,并且它们都有一个使用包含保护符号(例如 __CONSTANTS_H__)的标头,那么您将无法在给定的编译单元中成功地 #include 两个标头。一个更好的解决方案是#pragma once,但是一些旧的编译器不支持这个。

      【讨论】:

      • 更重要的是,#pragma once 不是标准的,并且不能保证你的编译器的下一个版本仍然支持它,或者支持相同的语义。对我来说,这是不使用它的一个很好的理由。
      • @sbi: true - 通常情况下,它是可移植性和我上面描述的问题之间的权衡。我想你总是可以举出一些非常丑陋的样板来测试编译器和编译器版本,然后相应地使用包含保护或#pragma once,但我不确定我是否希望在每个标题中都看到它。
      • 如果两个包含守卫发生冲突,答案是改变其中一个,而不是省略...
      • @Mephane:这不是使用第三方库时的理想解决方案
      • 那么你改变你自己的碰撞包含防护。如果两个第三方库的包含防护发生冲突......您可以提供一个包装器(在其自己的包含防护内)取消定义冲突符号,然后包含有问题的第三方标头。但这无论如何都是题外话。
      【解决方案5】:

      假设您有一个第三方库,并且您无法修改其代码。现在假设包含该库中的文件会生成编译器警告。您通常希望在高警告级别编译您自己的代码,但这样做会因使用该库而产生大量警告。您可以编写警告禁用器/启用器标头,然后您可以将其包裹在第三方库中,并且它们应该能够被多次包含。

      另一种更复杂的用法是 Boost 的预处理器迭代构造: http://www.boost.org/doc/libs/1_46_0/libs/preprocessor/doc/index.html

      【讨论】:

      • 我不明白在所述包装器中不包含保护会有什么不同。如果您已经包含了包装器,则警告已禁用 that 中包含的第三方标头,并且第二次包含包装器时,它会被包含保护器捕获,而不是任何东西完全没有。
      • @Mephane:这是包含编译器特定警告控制编译指示的标头,没有包含保护,而不是警告飞溅的库标头。
      【解决方案6】:

      #pragma once 的问题以及它不是标准的一部分的原因在于它并不总是在任何地方都有效。编译器如何知道两个文件是否是同一个文件,如果包含在不同的路径中?

      想一想,如果编译器出错并且未能包含它应该包含的文件,会发生什么?如果它包含一个文件两次,它不应该有会发生什么?你会如何解决这个问题?

      使用包含守卫,可能发生的最坏情况是编译需要更长的时间。

      编辑: 查看 comp.std.c++ 上的这个线程“#pragma once in ISO 标准了吗?”

      http://groups.google.com/group/comp.std.c++/browse_thread/thread/c527240043c8df92

      【讨论】:

      • 我并不是特别要求 #pragma once#define 包含守卫之间的区别,而是针对您特别希望多次包含文件的有效情况。
      • 好吧,我想你问为什么我们默认没有#pragma once。就像最后一句...
      • 我在询问更多关于所需效果本身的信息; #pragma once 是实现这种效果的一种方式,include-guards 也是如此。
      • 是的,#pragma once 很好,除非它不起作用。 :-) 没有办法让它始终工作,因此语言委员会决定将它包含在语言中。如果它适合您,使用您拥有的编译器、操作系统和网络设置,很好!
      猜你喜欢
      • 2016-04-13
      • 2013-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多