【问题标题】:Dos and Don'ts of Conditional Compile [closed]条件编译的注意事项[关闭]
【发布时间】:2010-12-17 10:36:54
【问题描述】:

什么时候做条件编译是个好主意,什么时候是一个糟糕透顶的主意?

条件编译是指使用#ifdefs 仅在特定条件下编译特定代码位。 #defineds 本身可能位于通用头文件中,也可能通过 -D 编译器指令引入。

【问题讨论】:

  • 这个问题太不具体了,在我看来没有明确的答案。您能否提供一个您正在考虑使用条件编译的具体场景?
  • 我见过某些情况,我认为这做得相当糟糕(在我看来),我认为将它扔给社区是个好主意。我相信我们可以提出一些好的指导方针。
  • @Space_C0wb0y 我认为这可能值得社区维基使用,而不一定是“不具体的”。在某些特定情况下,预处理器确实是一个好主意,而在其他一些情况下则不是。
  • 你缺代表还是什么?

标签: c++ c coding-style


【解决方案1】:

好主意:

  • 头部防护(你不能做得更好)
  • 条件实现(处理平台差异)
  • 调试特定检查(断言等...)
  • 根据建议:extern "C" {},以便 C++ 实现和 API 的 C 客户端可以使用相同的标头

坏主意:

  • 在编译标志之间更改 API,因为它会强制客户端使用相同的编译标志更改其用途... urk!

【讨论】:

  • 我不知道您的“好主意”部分中的第二点是否真的有效,考虑到诸如 boost/APR 等可移植库。我应该想象很少有极端情况可以有一些特定于平台的东西需要在这样的块中处理。我认为在这种特殊情况下,您最好依靠 Makefile 为平台构建适当的实现,而不是在同一文件中条件编译块...
  • 还有一个好主意:有条件地在头文件中编译 extern "C" {,用于将 C API 暴露给 C++。坏主意应该是“其他一切”。
  • @Nim:我同意,我目前正在关注 Clang 项目,其中包括 Howard Hinnant 的 libc++,所以我的心态非常低级 :) @JeremyP:感谢,至于“其他一切”,虽然这很诱人,但我更喜欢避免笼统的概括。
  • Nim:boost 太胖了,飞不起来,但是当你真正需要的只是几个 ifdef 时,即使将 APR 作为依赖项引入也常常是不合理的。另一方面,如果您从未真正最终在其中一个条件中运行代码,那么 ifdef 就很糟糕。但这并不是普通条件代码的 ifdef 所独有的。
  • @kusma,问题是“一对 ifdefs”不一定是一对,但真的吗?
【解决方案2】:

不要将 ifdef 放在你的代码中。
它真的很难阅读和理解。请让代码尽可能易于维护者阅读(他知道你住在哪里并拥有一把 Axe)。

将条件代码隐藏在单独的函数中,并使用 ifdef 定义正在使用的函数。

不要使用 else 部分进行定义。如果您认为您是在说一个平台是独一无二的,而所有其他平台都是一样的。这不太可能,更有可能的是您知道在几个平台上会发生什么,但是您应该使用#else 部分来粘贴#error,因此当它被移植到新平台时,开发人员必须明确修复条件他的平台。

x.h

#if defined(WINDOWS)
#define   MyPlatfromSleepSeconds(x)  sleep(x * 1000)
#elif defined (UNIX)
#define   MyPlatfromSleepSeconds(x)  Sleep(x)
#else
#error "Please define appropriate sleep for your platform"
#endif

不要试图将宏扩展为多行代码。这会导致疯狂。

p.h

#if defined(SOLARIS_3_1_1)
#define  DO_SOME_TASK(x,y)      doPartA(x);   \
                                doPartB(y);   \
                                couple(x,y)
#elif defined(WINDOWS)
#define  DO_SOME_TASK(x,y)      doAndCouple(x,y)
#else
#error "Please define appropriate DO_SOME_TASK for your platform"
#endif

如果您在 windows 上开发代码,然后在 solaris 3_1_1 上进行测试,您可能会在人们执行以下操作时发现意想不到的错误:

int loop;
for(loop = 0;loop < 10;++loop)
    DO_SOME_TASK(loop,loop);             // Windows works fine()
                                         // Solaras. Only doPartA() is in the loop.
                                         //          The other statements are done when the loop finishes 



 

【讨论】:

  • x.h:只是为了正确:API 混淆:unix 知道 sleep(seconds),Win Sleep(ms)(我确定 Loki 知道,只是笔误......)跨度>
  • p.h:只是想注释:更准确地说:在同一个宏中有多个语句(将所有内容放在一行上不会改变任何内容);另一方面:一个人可以做这样的事情,如果一个人做对了(例如,通过将语句包装在 do { /*...*/} while(false) 语句中)。无论如何,我不想传播这一点,其他方式(如函数内联)在大多数情况下更合适。
【解决方案3】:

基本上,您应该尽量将有条件编译的代码量保持在最低限度,因为您应该尝试测试所有这些,而有很多条件会使这变得更加困难。它还降低了代码的可读性;有条件地编译整个文件更加清晰,例如,通过将特定于平台的代码放在每个平台的单独文件中,并且从问题的其余部分的角度来看,它们都具有相同的 API。还要尽量避免在函数头中使用它;再说一次,那是因为那是一个特别令人困惑的地方。

但这并不是说你永远不应该使用条件编译。尽量保持简短和最小。 (在可能的情况下,我使用条件编译来控制其他宏的定义,然后在其余代码中使用这些宏;至少对我来说这似乎更清楚。)

【讨论】:

    【解决方案4】:

    当您不知道自己在做什么时,这是一个坏主意。当您以这种方式有效地解决问题时,这可能是一个好主意:)。

    按照您描述条件编译的方式,包含保护是其中的一部分。使用它不仅是一个好主意。这是一种避免编译错误的方法。

    对我来说,条件编译也是针对多个编译器和操作系统的一种方式。我参与了一个应该可以在 Windows XP 和更新版本、32 位或 64 位、使用 MinGW 和 Visual C++、在 Linux 32 和 64 位上使用 gcc/g++ 以及在 MacOS 上使用我不知道编译的库-what(我没有维护它,但我认为它是一个 gcc 端口)。如果没有预处理器条件,几乎不可能创建可在任何地方编译的单个源文件。

    【讨论】:

      【解决方案5】:

      条件编译的另一个实用用途是“注释掉”包含标准“C”cmets(即/* */)的代码段。一些编译器不允许嵌套这些 cmets,例如:

      /* comment out block of code
      
      .... code ....
      /* This is a standard 
       * comment.
       */  ... oopos!  Some compilers try to compile code after this closing comment.
      .... code ....
      
      end of block of code*/
      

      (正如您在语法高亮中看到的,StackOverflow 没有嵌套 cmets。)

      相反,您可以使用#ifdef 来获得正确的效果,例如:

      #ifdef _NOT_DEFINED_
      
      .... code ....
      /* This is a standard 
       * comment.
       */  
      .... code ....
      
      #endif
      

      【讨论】:

      • 为什么不使用更简单的#if 0
      • __NOT_DEFINED__ 保留给实现(根据语言规范),因为它包含双下划线。
      • 好点。将编辑原始帖子以包含一个前导下划线。
      【解决方案6】:

      在过去,如果您想生成真正可移植的代码,则必须求助于某种形式的条件编译。随着可移植库(例如 APR、boost 等)的激增,恕我直言,这个原因没有什么意义。如果您使用条件编译只是编译出特定构建不需要的代码块,您应该重新审视您的设计 - 我应该想象这将成为维护的噩梦。

      说了这么多,如果你确实需要使用条件编译,我会尽可能地远离代码主体,并限制在非常容易理解的非常具体的情况下。

      【讨论】:

        【解决方案7】:

        良好/合理的用途基于成本/收益分析。显然,这里的人非常清楚风险:

        • 在链接具有不同版本的类、函数等的对象时。
        • 使代码难以理解、测试和推理

        但是,有些用途通常属于净收益类别:

        • 头球后卫
        • 不同软件“生态系统”的代码定制,例如 Linux 与 Windows、Visual C++ 与 GCC、CPU 特定的优化,有时还有字长和字节序因素(尽管使用 C++,您通常可以在编译时通过模板黑客来确定这些,但是这可能会更加混乱)- 抽象出较低级别的差异以在这些环境中提供一致的 API
        • 与使用预处理器定义选择 API 版本、标准、行为、线程安全、协议等的现有代码进行交互(可悲但真实)
        • 在可用时可能使用可选功能的编译(想想 GNU 配置脚本以及它们在操作系统接口上执行的所有测试等)
        • 请求在翻译单元中生成额外代码,例如为独立应用添加main(),而不是为库添加main()
        • 控制不同逻辑构建模式(例如调试和发布)的代码包含

        【讨论】:

          【解决方案8】:

          这总是一个坏主意。它所做的是有效地创建源代码的多个版本,所有这些都需要测试,至少可以说这是一种痛苦。不幸的是,就像许多坏事一样,它有时是不可避免的。在编写需要在 Windows 和 Linux 之间移植的代码时,我很少使用它,但如果我发现自己经常这样做,我会考虑替代方案,例如拥有两个独立的开发子树。

          【讨论】:

          • 我觉得你在这里太笼统了。条件编译并不总是一个坏主意,但一些常见的用例是。
          • 我不能同意。这就像说“指针不好,因为你会被搞砸”。条件编译可以用对了,然后就不会太伤人了。
          • "create multiple versions..." 这不是条件编译的错误,如果您需要创建不同版本的代码(如多平台代码),那么无论如何您编写它的方式需要测试多个版本。
          • “两棵独立的开发树”怎么比条件编译好?
          • @unquiet:你不需要管理条件编译。使用条件编译,您可以在源代码本身中编写“差异”。如果您迁移到多个开发树那么您的维护变得很头疼,并且那么您需要 VCS 和其他工具来管理您的重复源代码。
          猜你喜欢
          • 2012-01-31
          • 1970-01-01
          • 2011-01-14
          • 1970-01-01
          • 1970-01-01
          • 2014-10-12
          • 1970-01-01
          • 1970-01-01
          • 2011-06-14
          相关资源
          最近更新 更多