Подтвердить что ты не робот

С++: зависимые от платформы типы - лучший шаблон

Я ищу шаблон для организации заголовочных файлов для нескольких платформ на С++.

У меня есть файл-оболочка .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...

Мне действительно не нравится материал препроцессора на С++. Есть ли чистый (и нормальный) способ сделать это лучше?

4b9b3361

Ответ 1

Вы должны использовать конфигурацию script, способную выполнять проверки платформы и генерировать соответствующие флаги компилятора и/или файлы заголовков конфигурации.

Есть несколько инструментов, которые могут выполнять эту задачу, например autotools, Scons или Cmake.

В вашем случае я бы рекомендовал использовать CMake, так как он прекрасно интегрируется с Windows, может создавать файлы проектов Visual Studio, а также make файлы Mingw.

Основная философия этих инструментов заключается в том, что вы не тестируете снова сами ОС, а против функций, которые могут быть или не быть, или для каких значений могут отличаться, уменьшая риск того, что ваш код не скомпилируется с помощью "платформа не поддерживается".

Вот прокомментированный образец 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 @[email protected]
/* The size of `float', as computed by sizeof. */
#define SIZEOF_FLOAT @[email protected]
/* The size of `long double', as computed by sizeof. */
#define SIZEOF_LONG_DOUBLE @[email protected]

Я приглашаю вас посетить сайт cmake, чтобы узнать больше об этом инструменте.

Я лично поклонник cmake, который я использую для своих личных проектов.

Ответ 2

Один из способов сделать это - установить заголовки, специфичные для ОС, в разные каталоги и определить, какой каталог найден, передав этот каталог компилятору через флаг -I.

В вашем случае вы просто имели бы

#include "defs.h"

Ответ 3

Нет ничего по своей сути "плохо" в использовании препроцессора. Он был создан по какой-то причине, и условная компиляция/включение, подобная этому, является одной из вещей, которые делают довольно хорошо.

Ключ должен сохранять читаемый код. Если в вашем коде есть много блоков #ifdef WIN32, или если вы обнаружите, что делаете это в большом количестве файлов, вот несколько советов по очистке вашего кода.

1) Переместите условное включение в отдельный файл. Ваши исходные файлы будут просто #include <defs.h> и переместите ваш условный блок включения в defs.h(даже если все это в этом файле).

2) Пусть ваш makefile определит, какой файл включить. Опять же, ваши исходные файлы будут просто #include <defs.h>. Ваш make файл обнаружит текущую платформу и добавит -iwin32 или -ilinux в строку параметров компилятора, если это необходимо. Это подход, который я обычно беру. Вы можете выполнять работу по обнаружению платформы внутри make файла или на этапе конфигурации (если вы динамически генерируете свои файлы make файлов). Я также считаю этот вариант самым легким с точки зрения добавления поддержки новой платформы на более поздний срок; это минимизирует количество изменений, которые необходимо внести.

3) Если вы строите на системе Linux (подразумевая, что любой код Win32 будет сгенерирован кросс-компилятором), вы можете создать скрипты сборки для символической ссылки на соответствующий каталог файлов заголовков. Ваш код может ссылаться на каталог как #include <platform\defs.h> и позволить makefile или configure script беспокоиться о символической привязке к правильной папке.

Ответ 4

Препроцессор - это не плохая вещь сама по себе. Это мощный инструмент, как и многие другие инструменты для разработки. Вопрос в том, как вы его используете?

Если вы делаете свою предварительную обработку прозрачной, документированной и логичной, это не проблема вообще. Это когда вы начинаете делать "умные" трюки, которые трудно понять, что это становится проблемой.

В идеале вы бы ограничили, как часто вы это делаете. Таким образом, возможно, у вас есть defs.h, который вы включите во все ваши файлы, и внутри этого одного файла вы будете использовать свою конкретную логику ОС.

Ответ 5

Это действительно зависит от детализации фактических различий между реализациями для определенных платформ (в вашем коде это соответствует "общим словам ниже" ).

Когда различия значительны, я часто предпочитаю учитывать все зависящие от платформы коды к нескольким компонентам (.cpp), использующим один и тот же платформенный независимый интерфейс (.hpp).

Например, предположим, что нам нужно получить изображения с камеры FireWire в кросс-платформенной прикладной программе. Пути достижения этого на разных платформах безнадежно отличаются. Тогда имеет смысл иметь общий интерфейс, который будет помещен в CameraIeee1394.hpp, и поместить две зависимые от платформы реализации в CameraIeee1394_unix.cpp и CameraIeee1394_w32.cpp.

script, который подготавливает make файл для конкретной платформы, может автоматически игнорировать исходные файлы для иностранных платформ, просто проверяя суффикс имени файла.

Ответ 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

Если вы держите его хорошо структурированным, у вас не должно возникнуть проблемы с его чистотой и разумностью.:)

Вы также можете использовать typedefs, но это может привести к другим проблемам, если вы хотите перегрузить тип позже в своем приложении.