【问题标题】:C++: Platform dependent types - best pattern [closed]C ++:平台相关类型 - 最佳模式 [关闭]
【发布时间】:2011-03-17 00:45:57
【问题描述】:

我正在寻找一种在 C++ 中为多个平台组织头文件的模式。

我有一个可以在 Linux 和 Win32 下编译的包装器 .h 文件。

这是我能做到的最好的吗?

// defs.h

#if defined(WIN32)
#include <win32/defs.h>
#elif defined(LINUX)
#include <linux/defs.h>
#else
#error "Unable to determine OS or current OS is not supported!"
#endif

//  Common stuff below here...

我真的不喜欢 C++ 中的预处理器。有没有一种干净(和理智)的方法可以更好地做到这一点?

【问题讨论】:

  • 使用平台相关的makefile。
  • 预处理器只有在过度使用时才会出现问题,这对我来说看起来简单易懂
  • 我曾经在介绍 automake/autoconf 教程时读到的最好的提示是,完成程序而不是担心它在 16 - 64 位上的性能更重要。在与 automake 打交道的几个无休止的夜晚之后,我意识到它是多么真实。无论如何都会故意添加预处理器语句......(由您或其他人)
  • 如果您不想处理预处理器,请尝试使用 autotools。但请不要误会,这是不理智的方式。

标签: c++ windows linux header


【解决方案1】:

预处理器本身并不是一件坏事。它是一个强大的工具,就像许多其他开发工具一样。问题是你如何使用它?

如果您的预处理过程清晰、记录在案且符合逻辑,那么这根本不是问题。当您开始使用难以破译的“聪明”技巧时,它就会成为问题。

理想情况下,您会限制执行此操作的频率。因此,您可能会有一个 defs.h 包含在所有文件中,并在该文件中执行特定于操作系统的逻辑。

【讨论】:

    【解决方案2】:

    一种方法是将操作系统特定的头文件放入不同的目录,并通过 -I 标志将该目录传递给编译器来控制找到哪个目录。

    在你的情况下,你会拥有

    #include "defs.h"
    

    【讨论】:

    • @tenpn - 关键是你不需要#ifdefs
    • 这就是我的意思。缺少#ifdefs 是为了胜利。如果你不小心,我会带走你的投票! :)
    • 我喜欢这个,因为它让一切井井有条,并减少了出错的可能性(没有#ifdef)。更好的是,文件“defs.h”可以通过autotools等生成。
    • 就我个人而言,我认为这是一种讨厌的方法,因为您在做什么并不立即显而易见,这会导致代码无法维护。在我看来,详细说明此类事情总是比“技巧”更可取。
    • @Goz - 在这种情况下,它可能看起来更简单,因为您可以看到正在做什么,但是随着您获得更多的复杂性,您最终会得到每行代码都有 #ifdefs 的代码如果分离出来,这将更容易阅读 - 请参阅chris-lott.org/resources/cstyle/ifdefs.pdf 了解#ifdef 复杂性
    【解决方案3】:

    您应该使用能够执行平台检查并生成适当的编译器标志和/或配置头文件的配置脚本。

    有多种工具可以执行此任务,例如 autotoolsSconsCmake

    在您的情况下,我建议使用 CMake,因为它与 Windows 很好地集成,能够生成 Visual Studio 项目文件以及 Mingw makefile。

    这些工具背后的主要理念是,您无需再次测试操作系统本身,而是针对可能存在或可能不存在的功能,或者值可能不同的功能,从而降低您的代码编译失败的风险平台不支持”错误。

    这是一个带注释的 CMake 示例 (CMakeFiles.txt):

    # platform tests
    include(CheckFunctionExists)
    include(CheckIncludeFile)
    include(CheckTypeSize)
    include(TestBigEndian)
    
    check_include_file(sys/types.h  HAVE_SYS_TYPES_H)
    check_include_file(stdint.h     HAVE_STDINT_H)
    check_include_file(stddef.h     HAVE_STDDEF_H)
    check_include_file(inttypes.h   HAVE_INTTYPES_H)
    
    check_type_size("double"      SIZEOF_DOUBLE)
    check_type_size("float"       SIZEOF_FLOAT)
    check_type_size("long double" SIZEOF_LONG_DOUBLE)
    
    test_big_endian(IS_BIGENDIAN)
    if(IS_BIGENDIAN)
      set(WORDS_BIGENDIAN 1)
    endif(IS_BIGENDIAN)
    
    # configuration file
    configure_file(config-cmake.h.in ${CMAKE_BINARY_DIR}/config.h)
    include_directories(${CMAKE_BINARY_DIR})
    add_definitions(-DHAVE_CONFIG_H)
    

    这样,您必须提供一个 config-cmake.h.in 模板,该模板将由 cmake 处理以生成包含您需要的定义的 config.h 文件:

    /* Define to 1 if you have the <sys/types.h> header file. */
    #cmakedefine HAVE_SYS_TYPES_H
    /* Define to 1 if you have the <stdint.h> header file. */
    #cmakedefine HAVE_STDINT_H
    /* Define to 1 if your processor stores words with the most significant byte
       first (like Motorola and SPARC, unlike Intel and VAX). */
    #cmakedefine WORDS_BIGENDIAN
    /* The size of `double', as computed by sizeof. */
    #define SIZEOF_DOUBLE @SIZEOF_DOUBLE@
    /* The size of `float', as computed by sizeof. */
    #define SIZEOF_FLOAT @SIZEOF_FLOAT@
    /* The size of `long double', as computed by sizeof. */
    #define SIZEOF_LONG_DOUBLE @SIZEOF_LONG_DOUBLE@
    

    我邀请您访问 cmake 网站以了解有关此工具的更多信息。

    我个人是 cmake 的粉丝,我将其用于我的个人项目。

    【讨论】:

      【解决方案4】:

      使用预处理器本身并没有什么“坏处”。它的创建是有原因的,像这样的条件编译/包含是做得相当好的事情之一。

      关键是让您的代码保持可读性。如果您最终在代码中包含大量 #ifdef WIN32 块,或者您发现自己在大量文件中执行此操作,这里有一些提示可以稍微清理您的代码。

      1) 将条件包含移到单独的文件中。您的源文件将简单地#include &lt;defs.h&gt;,并将您的条件包含块移动到 defs.h(即使这就是该文件中的全部内容)。

      2) 让您的 makefile 确定要包含的文件。同样,您的源文件将只是 #include &lt;defs.h&gt;。您的 makefile 将检测当前平台并酌情将-iwin32-ilinux 添加到编译器选项字符串。这是我通常采用的方法。您可以在 makefile 中或在配置阶段(如果您正在动态生成 makefile)进行平台检测工作。我还发现这个选项在以后添加对新平台的支持方面最简单;它似乎最大限度地减少了需要进行的更改。

      3) 如果您在 Linux 系统上构建(暗示任何 Win32 代码都将由交叉编译器生成),您可以让构建脚本创建指向相应头文件目录的符号链接。您的代码可以将目录引用为 #include &lt;platform\defs.h&gt; 并让 makefile 或配置脚本担心符号链接正确的文件夹。

      【讨论】:

      【解决方案5】:

      这实际上取决于特定平台的实现之间的实际差异的粒度(在您的代码中,这对应于“此处的常见内容”)。

      当差异很大时,我通常更愿意将所有平台相关代码分解为共享相同平台无关接口 (.hpp) 的多个组件 (.cpp)。

      例如,假设我们需要从跨平台应用程序中的火线相机获取图像。在不同平台上实现这一目标的方法完全不同。然后有一个通用接口放在 CameraIeee1394.hpp 中,并将两个平台相关的实现放在 CameraIeee1394_unix.cpp 和 CameraIeee1394_w32.cpp 中是有意义的。

      为特定平台准备makefile的脚本可以通过简单地检查文件名后缀来自动忽略外国平台的源文件。

      【讨论】:

      • 非常好的建议,虽然并没有真正解决这个关于类型的特殊问题。
      【解决方案6】:

      如果您要使用预处理器,现在是时候使用它了。有不同的解决方案,但我相信你可以用干净的方式解决这个问题。

      我通常解决它的方法是我首先创建一个进行平台和编译器检测的头文件,例如:

      #define LIB_PLATFORM_WIN32 (1)
      #define LIB_PLATFORM_LINUX (2)
      
      #if defined(WIN32)
         #define LIB_PLATFORM LIB_PLATFORM_WIN32
      #elif defined(LINUX)
         #define LIB_PLATFORM LIB_PLATFORM_LINUX
      #endif
      

      现在您已经有了自己的平台;然后,您可以在整个代码中使用自己的定义。

      例如,如果你想要一个独立于编译器的 64 位整数,你可以使用这个

      #if LIB_COMPILER == LIB_COMPILER_MSVC
          #define int64   __int64
          #define uint64  unsigned __int64
       #else
          #define int64   long long
          #define uint64  unsigned long long
      #endif
      

      如果你保持它的结构良好,那么保持它的清洁和理智应该不会有问题。 :)

      您也可以使用 typedef,但如果您想稍后在应用程序中重载类型,这可能会带来其他问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-11-28
        • 1970-01-01
        • 2019-07-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-28
        • 1970-01-01
        相关资源
        最近更新 更多