有条件编译的注意事项

时间:2010-12-17 10:36:55

标签: c++ c coding-style

什么时候进行条件编译是一个好主意,何时是一个非常糟糕的主意?

通过条件编译,我的意思是使用#ifdef来仅在某些条件下编译某些代码。 #defined本身可以位于公共头文件中,也可以通过-D编译器指令引入。

8 个答案:

答案 0 :(得分:7)

好主意:

  • 标题警卫(你的便携性无法做得更好)
  • 有条件实施(处理平台差异)
  • 调试特定检查(断言等......)
  • 根据建议:extern "C" {},以便C ++实现和API的C客户端可以使用相同的标头

糟糕的主意:

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

答案 1 :(得分:2)

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

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

答案 2 :(得分:2)

不要在代码中加入ifdef 这使得阅读和理解变得非常困难。请使代码尽可能易于阅读维护者(他知道你住在哪里并拥有一个Ax)。

将条件代码隐藏在单独的函数中,并使用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 

答案 3 :(得分:1)

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

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

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

答案 4 :(得分:1)

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

/* 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不会嵌套注释。)

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

#ifdef _NOT_DEFINED_

.... code ....
/* This is a standard 
 * comment.
 */  
.... code ....

#endif

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

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

  • 用于链接看到不同版本的类,函数等的对象。
  • 使代码难以理解,测试和推理

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

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

答案 7 :(得分:-4)

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