C++:平台相关类型 - 最佳模式

22

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

我有一个包装器 .h 文件,应该在 Linux 和 Win32 下编译。

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

// 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++中的预处理器。有没有更干净(和理智)的方法来更好地完成这项工作?


1
使用平台相关的makefile。 - rwong
9
如果过度使用预处理器,它就成为一个问题。对我来说,这看起来足够简单易懂。 - Patrick
我曾在一篇automake/autoconf教程的介绍中读到过最好的提示,那就是重要的是将程序完成,而不是担心它在16-64位上的性能表现。经过几个漫长的晚上与automake打交道后,我意识到这是多么正确。而且预处理语句也会被有意添加...(无论是你还是别人)。 - tux21b
1
如果你不想处理预处理器,可以尝试使用Autotools。但毫无疑问,这并不是一个明智的选择。 - Michael Foukarakis
6个回答

19

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

有几个工具可以执行此任务,例如autotools, SconsCmake

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

这些工具背后的主要哲学是,您不再针对操作系统本身进行测试,而是针对可能存在或不存在的功能或其值可能会变化的特性进行测试,从而降低了代码无法编译的风险,出现"平台不受支持"的错误。

这里是一个有注释的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的粉丝,我在我的个人项目中使用它。


6

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

在您的情况下,您只需要

#include "defs.h"

@tenpn - 关键是你不需要 #ifdefs。 - mmmmmm
这就是我的意思。缺少#ifdefs正是为了胜利。如果你不小心,我会拿走你的点赞! :) - tenpn
我喜欢这个因为它可以使所有东西保持良好的组织,并减少错误的可能性(没有 #ifdef's)。更好的是,文件“defs.h”可以由autotools等生成。 - John L
3
我个人认为这是一种恶劣的方法,因为它并不立即明显你在做什么,这会导致代码难以维护。在我看来,对于这样的事情,详细说明总是比“技巧”更可取。 - Goz
@Goz - 在给定的情况下,显式可能更简单,因为您可以看到正在执行什么操作,但是随着复杂性的增加,您最终会得到每行代码都有#ifdefs的代码,如果将其分开阅读,则会更容易阅读-请参见http://www.chris-lott.org/resources/cstyle/ifdefs.pdf以获取#ifdef复杂性。 - mmmmmm

4

使用预处理器本身并没有什么“坏处”。它是有原因存在的,条件编译/包含就是其中做得相当不错的一件事情。

关键在于保持代码可读性。如果你的代码中有很多 #ifdef WIN32 块,或者在大量文件中都这样做,以下是一些清理代码的小技巧。

1)将条件包含移到单独的文件中。您的源文件只需 #include <defs.h>,然后将条件包含块移动到 defs.h 中(即使该文件中只有这个内容)。

2)让您的 makefile 确定要包含哪个文件。同样,您的源文件只需 #include <defs.h>。您的 makefile 将检测当前平台,并根据需要将 -iwin32-ilinux 添加到编译器选项字符串中。这是我通常采用的方法。您可以在 makefile 中或配置阶段(如果您正在动态生成 makefile)中执行平台检测工作。我还发现这种选项最容易在以后添加对新平台的支持;它似乎最小化了需要进行的更改数量。

3)如果您正在 Linux 系统上构建(意味着任何 Win32 代码都将由交叉编译器生成),则可以让您的构建脚本创建到适当头文件目录的符号链接。您的代码可以将该目录引用为 #include <platform\defs.h>,并让 makefile 或配置脚本担心符号链接到正确的文件夹。


这里应该是 #if _WIN32,而不是 WIN32... http://msdn.microsoft.com/en-us/library/b0084kay%28v=VS.100%29.aspx - rubenvb

2
预处理器本身并不是一个坏东西。它是一个强大的工具,就像许多其他开发工具一样。问题在于你如何使用它?
如果你使你的预处理清晰、有文档记录和逻辑性强,那么它就不是一个问题。只有当你开始做一些难以理解的“聪明”技巧时,它才成为一个问题。
最好的情况是尽量减少这种情况的发生。因此,你可能会有一个defs.h文件,将其包含在所有文件中,在这个文件中进行你的特定于操作系统的逻辑处理。

1

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

当差异很大时,我通常更喜欢将所有依赖于平台的代码分解为几个组件(.cpp),并共享相同的平台无关接口(.hpp)。

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

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


非常好的建议,尽管它并没有真正解决关于类型的这个特定问题。 - Ben Voigt

0
如果你要使用预处理器,那么现在就是使用它的时候了。有不同的解决方案,但我相信你可以用一种干净的方式来解决这个问题。
我通常的解决方法是首先创建一个头文件,进行平台和编译器检测,例如:
#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

如果你保持良好的结构,就不应该有问题保持它的整洁和合理。:)

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接