在代码中使用#ifdef是不好的做法吗?

RLT*_*RLT 16 c++ macos coding-style

我必须使用#ifdef i386x86_64作为特定于体系结构的代码,有时需要#ifdef MAC或#ifdef WIN32 ...等等,以便进行特定于平台的代码.

我们必须保持公共代码库和便携式.

但我们必须遵循使用#ifdef严格禁止的指导原则.我不明白为什么?

作为这个问题的扩展,我还想了解何时使用#ifdef?

例如,dlopen()在从64位进程运行时无法打开32位二进制,反之亦然.因此它更具体的架构.在这种情况下我们可以使用#ifdef吗?

Jer*_*fin 13

#ifdef不是编写可移植代码,而是编写多个特定于平台的代码.不幸的是,在许多(大多数?)情况下,您很快就会得到几乎无法穿透的便携式和平台特定代码.

您还经常#ifdef被用于可移植性以外的目的(定义要生成的代码的"版本",例如将包括什么级别的自我诊断).不幸的是,这两者经常互动,并且交织在一起.例如,有人将一些代码移植到MacOS决定它需要更好的错误报告,他补充说 - 但它使其特定于MacOS.后来,别人决定了更好的错误报告将在Windows非常有用的,所以他能够代码自动#define荷兰国际集团MACOS如果WIN32定义-但随后补充道"只是一对夫妇更" #ifdef WIN32排除一些代码,真的 MacOS的具体什么时候定义Win32.当然,我们还补充说MacOS基于BSD Unix这一事实,因此当定义MACOS时,它也会自动定义BSD_44 - 但是(再次)转向并在编译MacOS时排除一些BSD"东西".

这很快退化为代码,如下例所示(取自#ifdef Considered Harmful):

#ifdef SYSLOG
#ifdef BSD_42
openlog("nntpxfer", LOG_PID);
#else
openlog("nntpxfer", LOG_PID, SYSLOG);
#endif
#endif
#ifdef DBM
if (dbminit(HISTORY_FILE) < 0)
{
#ifdef SYSLOG
    syslog(LOG_ERR,"couldn’t open history file: %m");
#else
    perror("nntpxfer: couldn’t open history file");
#endif
    exit(1);
}
#endif
#ifdef NDBM
if ((db = dbm_open(HISTORY_FILE, O_RDONLY, 0)) == NULL)
{
#ifdef SYSLOG
    syslog(LOG_ERR,"couldn’t open history file: %m");
#else
    perror("nntpxfer: couldn’t open history file");
#endif
    exit(1);
}
#endif
if ((server = get_tcp_conn(argv[1],"nntp")) < 0)
{
#ifdef SYSLOG
    syslog(LOG_ERR,"could not open socket: %m");
#else
    perror("nntpxfer: could not open socket");
#endif
    exit(1);
}
if ((rd_fp = fdopen(server,"r")) == (FILE *) 0){
#ifdef SYSLOG
    syslog(LOG_ERR,"could not fdopen socket: %m");
#else
    perror("nntpxfer: could not fdopen socket");
#endif
    exit(1);
}
#ifdef SYSLOG
syslog(LOG_DEBUG,"connected to nntp server at %s", argv[1]);
#endif
#ifdef DEBUG
printf("connected to nntp server at %s\n", argv[1]);
#endif
/*
* ok, at this point we’re connected to the nntp daemon
* at the distant host.
*/
Run Code Online (Sandbox Code Playgroud)

这是一个相当小的例子,只涉及一些宏,但阅读代码已经很痛苦了.我亲眼看到(并且不得不处理)在实际代码中糟糕.这里的代码很丑陋,阅读起来很痛苦,但要弄清楚在什么情况下使用哪些代码仍然相当容易.在许多情况下,您最终会得到更复杂的结构.

为了给出一个具体的例子,我希望看到这些内容,我会做这样的事情:

if (!open_history(HISTORY_FILE)) {
    logerr(LOG_ERR, "couldn't open history file");
    exit(1);
}

if ((server = get_nntp_connection(server)) == NULL) {
    logerr(LOG_ERR, "couldn't open socket");
    exit(1);
}

logerr(LOG_DEBUG, "connected to server %s", argv[1]);
Run Code Online (Sandbox Code Playgroud)

在这种情况下,这是可能的,我们LOGERR的定义是不是一个实际的功能的宏.它可能是非常微不足道的,有一个标题,如下所示:

#ifdef SYSLOG
    #define logerr(level, msg, ...) /* ... */
#else
    enum {LOG_DEBUG, LOG_ERR};
    #define logerr(level, msg, ...) /* ... */
#endif
Run Code Online (Sandbox Code Playgroud)

[目前,假设一个可以/将处理可变参数宏的预处理器]

鉴于你的主管的态度,即使这可能是不可接受的.如果是这样,那没关系.而是一个宏,而不是在函数中实现该功能.在其自己的源文件中隔离函数的每个实现,并构建适合目标的文件.如果您有很多特定于平台的代码,您通常希望将其隔离到自己的目录中,很可能使用自己的makefile 1,并且有一个顶层的makefile,它可以选择基于其调用的其他makefile.指定目标.


  1. 有些人不喜欢这样做.我并没有真正地讨论如何构建makefile,只是注意到某些人发现/认为有用的可能性.


Dim*_*ima 7

你应该#ifdef尽可能避免.IIRC,是斯科特·梅尔斯(Scott Meyers)用#ifdefs 编写的,你没有获得与平台无关的代码.相反,您会获得依赖于多个平台的代码.此外#define,#ifdef并不是语言本身的一部分. #define没有范围的概念,这可能会导致各种各样的问题.最好的方法是将预处理器的使用保持在最低限度,例如包含保护.否则你很可能会陷入混乱,这很难理解,维护和调试.

理想情况下,如果需要具有特定于平台的声明,则应该具有单独的特定于平台的包含目录,并在构建环境中适当地处理它们.

如果您具有某些功能的特定于平台的实现,则还应将它们放入单独的.cpp文件中,并在构建配置中再次将它们哈希.

另一种可能性是使用模板.您可以使用空虚拟结构表示您的平台,并将其用作模板参数.然后,您可以将模板专门化用于特定于平台的代码.这样,您将依赖编译器从模板生成特定于平台的代码.

当然,任何这种方法的唯一方法就是将特定于平台的代码非常干净地分解为单独的函数或类.


Mat*_* M. 7

我看过3个广泛的用法#ifdef:

  • 隔离平台特定代码
  • 隔离功能特定代码(并非所有版本的编译器/语言方言都相同)
  • 隔离编译模式代码(NDEBUG任何人?)

每个都有可能产生大量无法造成的代码,应该相应地对待,但并非所有这些都可以以相同的方式处理.


1.平台特定代码

每个平台都有自己的一套特定的包含,结构和功能来处理IO(主要)等事情.

在这种情况下,处理这种混乱的最简单方法是提供统一的前端,并具有特定于平台的实现.

理想的情况是:

project/
  include/namespace/
    generic.h
  src/
    unix/
      generic.cpp
    windows/
      generic.cpp
Run Code Online (Sandbox Code Playgroud)

这样,平台的东西全部保存在一个文件中(每个标题),因此很容易找到.该generic.h文件描述了接口,generic.cpp由构建系统选择.不#ifdef.

如果您想要内联函数(用于性能),那么genericImpl.i提供内联定义和特定平台的特定内容可以包含在generic.h文件的末尾#ifdef.


2.功能特定代码

这有点复杂,但通常只有图书馆才有.

例如,Boost.MPL使用具有可变参数模板的编译器更容易实现.

或者,支持移动构造函数的编译器允许您定义某些操作的更高效版本.

这里没有天堂.如果你发现自己处于这样的境地......你最终会得到类似Boost的文件(aye).


3.编译模式代码

一般情况下你可以侥幸逃脱#ifdef.传统的例子是assert:

#ifdef NDEBUG
#  define assert(X) (void)(0)
#else // NDEBUG
#  define assert(X) do { if (!(X)) { assert_impl(__FILE__, __LINE__, #X); } while(0)
#endif // NDEBUG
Run Code Online (Sandbox Code Playgroud)

然后,宏本身的使用不受编译模式的影响,因此至少混乱包含在单个文件中.

注意:这里有一个陷阱,如果在"ifdefed away"时宏没有扩展到某个语句,那么你有可能在某些情况下改变流量.此外,当混合中存在函数调用(具有副作用)时,不评估其参数的宏可能导致奇怪的行为,但是在这种情况下这是期望的,因为所涉及的计算可能是昂贵的.