Как ограничить влияние зависящих от реализации возможностей языка в C ++? - PullRequest
7 голосов
/ 09 марта 2009

Ниже приведен отрывок из книги Бьярна Страуструпа «Язык программирования C ++»:

Раздел 4.6:

Некоторые аспекты фундаментальных типов C ++, такие как размер int, определяются реализацией (§C.2). Я указываю на эти зависимости и часто рекомендую избегать их или предпринимать шаги, чтобы минимизировать их влияние. Почему ты должен беспокоиться? Люди, которые программируют на различных системах или используют различные компиляторы, очень заботятся, потому что если они этого не делают, они вынуждены тратить время на поиск и исправление неясных ошибок. Люди, которые утверждают, что их не волнует переносимость, обычно делают это, потому что они используют только одну систему и считают, что могут позволить себе придерживаться мнения, что «язык - это то, что реализует мой компилятор». Если ваша программа успешна, она, вероятно, будет портирована, поэтому кто-то должен будет найти и исправить проблемы, связанные с функциями, зависящими от реализации. Кроме того, программы часто должны быть скомпилированы с другими компиляторами для той же системы, и даже будущий выпуск вашего любимого компилятора может сделать некоторые вещи не так, как текущий. Намного проще узнать и ограничить влияние зависимостей реализации при написании программы, чем пытаться распутать беспорядок впоследствии.

Относительно влияние возможностей языка, зависящего от реализации, относительно просто.

Мой вопрос таков: как ограничить влияние возможностей языка, зависящих от реализации? Пожалуйста, укажите функции языка, зависящие от реализации, и покажите, как ограничить их влияние.

Ответы [ 8 ]

4 голосов
/ 09 марта 2009

Мало идей:

  • К сожалению, вам придется использовать макросы, чтобы избежать проблем с платформой или компилятором. Вы можете взглянуть на заголовки библиотек Boost, чтобы увидеть, что это может довольно легко стать громоздким, например, посмотрите на файлы:

  • Целочисленные типы, как правило, беспорядочные на разных платформах, вам придется определить свои собственные typedefs или использовать что-то вроде Boost cstdint.hpp

  • Если вы решите использовать какую-либо библиотеку, проверьте, поддерживается ли библиотека на данной платформе

  • Используйте библиотеки с хорошей поддержкой и четко документированной поддержкой платформы (например, Boost)

  • Вы можете абстрагироваться от некоторых проблем, связанных с реализацией C ++, сильно полагаясь на библиотеки, такие как Qt, которые предоставляют «альтернативу» в смысле типов и алгоритмов. Они также пытаются сделать кодирование в C ++ более переносимым. Это работает? Я не уверен.

  • Не все можно сделать с помощью макросов. Ваша система сборки должна быть в состоянии определить платформу и наличие определенных библиотек. Многие рекомендуют autotools для конфигурации проекта, с другой стороны, я рекомендую CMake (довольно приятный язык, не более M4)

  • Порядковый номер и выравнивание могут быть проблемой, если вы будете вмешиваться на низком уровне (т. Е. reinterpret_cast и friends одинаковые вещи (друзья были плохим словом в контексте C ++)).

  • добавляет много флагов предупреждений для компилятора, для gcc я бы рекомендовал по крайней мере -Wall -Wextra. Но это еще не все, см. Документацию компилятора или этот вопрос .

  • вам нужно следить за всем, что определяется реализацией и зависит от реализации. Если вам нужна правда, только правда, ничего, кроме правды, тогда переходите на стандарт ISO.

4 голосов
/ 09 марта 2009

Что ж, упомянутые выше размеры переменных - довольно хорошо известная проблема, с общим обходным путем предоставления версий базовых типов с неопределенными типами, которые имеют четко определенные размеры (обычно объявляемые в имени typedef). Это делается с помощью макросов препроцессора для обеспечения разной видимости кода на разных платформах. E.g.:

#ifdef __WIN32__
typedef int int32;
typedef char char8;
//etc
#endif
#ifdef __MACOSX__
//different typedefs to produce same results
#endif

Другие проблемы обычно решаются аналогичным образом (т. Е. С помощью токенов препроцессора для выполнения условной компиляции)

3 голосов
/ 09 марта 2009

Наиболее очевидной зависимостью реализации является размер целочисленных типов. Есть много способов справиться с этим. Наиболее очевидный способ - использовать typedef для создания целых чисел различных размеров:

 typedef signed   short  int16_t;
 typedef unsigned short  uint16_t;

Хитрость в том, чтобы выбрать конвенцию и придерживаться ее. Какое соглашение является сложной частью: INT16, int16, int16_t, t_int16, Int16 и т. Д. C99 имеет файл stdint.h, который использует стиль int16_t. Если у вашего компилятора есть этот файл, используйте его.

Точно так же вы должны быть педантичны в отношении использования других стандартных определений, таких как size_t, time_t и т. Д.

Другой трюк - знать, когда не следует использовать эти typedef. Управляющая переменная цикла, используемая для индексации массива, должна просто принимать необработанные типы int, чтобы компиляция генерировала лучший код для вашего процессора. for (int32_t i = 0; i

2 голосов
/ 09 марта 2009

Есть два подхода к этому:

  • определяет ваши собственные типы с известным размером и использует их вместо встроенных типов (например, typedef int int32 # if-ed для различных платформ)
  • использовать приемы, не зависящие от размера шрифта

Первое очень популярно, однако второе, когда это возможно, обычно приводит к более чистому коду. Это включает в себя:

  • не предполагать, что указатель может быть приведен к int
  • не предполагайте, что вы знаете размер байта отдельных типов, всегда используйте sizeof, чтобы проверить его
  • при сохранении данных в файлы или передаче их по сети используйте методы, которые переносимы при изменении размеров данных (например, сохранение / загрузка текстовых файлов)

Одним из недавних примеров этого является написание кода, который можно скомпилировать для платформ x86 и x64. Опасная часть здесь - указатель и размер size_t - будьте готовы, это может быть 4 или 8 в зависимости от платформы, при приведении или разнице указателя, никогда не приводите к int, используйте взамен intptr_t и аналогичные типы typedef-ed.

2 голосов
/ 09 марта 2009

Хорошим решением является использование общих заголовков, которые определяют типы typedeff как необходимые.

Например, включение sys / types.h является отличным способом решения этой проблемы, как и использование переносимых библиотек.

1 голос
/ 09 марта 2009

Одним из ключевых способов избежать зависимости от конкретных размеров данных является чтение и запись постоянных данных в виде текста, а не двоичных данных. Если необходимо использовать двоичные данные, то все операции чтения / записи должны быть централизованы с использованием нескольких методов и подходов, таких как описанные здесь typedef.

Второе, что вы можете сделать, это включить все предупреждения ваших компиляторов. например, использование флага -pedantic с g ++ предупредит вас о многих потенциальных проблемах переносимости.

0 голосов
/ 19 сентября 2009

Ниже приведен отрывок из книги Бьярна Страуструпа «Язык программирования C ++»:

Раздел 10.4.9:

Нет гарантий, независимых от реализации, о порядке построения нелокальных объектов в разных единицах компиляции. Например:

// file1.c:
    Table tbl1;
// file2.c:
    Table tbl2;

Будет ли tbl1 построен до tbl2 или наоборот, зависит от реализации. Порядок не гарантируется даже в каждой конкретной реализации. Динамическое связывание или даже небольшое изменение в процессе компиляции может изменить последовательность. Порядок уничтожения аналогичным образом зависит от реализации.

Программист может обеспечить правильную инициализацию, реализуя стратегию, которую реализации обычно используют для локальных статических объектов: переключение в первый раз. Например:

class Zlib {
    static bool initialized;
    static void initialize() { /* initialize */ initialized = true; }
public:
    // no constructor

    void f()
    {
        if (initialized == false) initialize();
        // ...
    }
    // ...
};

Если существует много функций, требующих проверки первого переключателя, это может быть утомительным, но часто управляемым. Этот метод основан на том факте, что статически размещенные объекты без конструкторов инициализируются в 0 . Действительно сложный случай - это тот случай, когда первая операция может быть критичной по времени, поэтому накладные расходы на тестирование и возможную инициализацию могут быть серьезными. В этом случае требуется дополнительная хитрость (§21.5.2).

Альтернативный подход для простого объекта - представить его как функцию (§9.4.1):

int& obj() { static int x = 0; return x; } // initialized upon first use

Новые переключатели не справляются со всеми возможными ситуациями. Например, можно создавать объекты, которые ссылаются друг на друга во время строительства. Таких примеров лучше избегать. Если такие объекты необходимы, они должны быть тщательно построены поэтапно.

0 голосов
/ 09 марта 2009

Если вы беспокоитесь о переносимости, такие вещи, как размер int, могут быть определены и обработаны без особых затруднений. Многие компиляторы C ++ также поддерживают функции C99, такие как типы int: int8_t, uint8_t, int16_t, uint32_t и т. Д. Если у вас их не поддерживается изначально, вы всегда можете включить <cstdint> или <sys/types.h>, который, чаще всего, имеет те typedef изд. <limits.h> имеет эти определения для всех основных типов.

Стандарт гарантирует только минимальный размер шрифта, на который вы всегда можете положиться: sizeof(char) < sizeof(short) <= sizeof(int) <= sizeof(long). char должно быть не менее 8 бит. short и int должны содержать не менее 16 бит. long должно быть не менее 32 бит.

Другие вещи, которые могут быть определены реализацией, включают ABI и схемы управления именами (в особенности поведение export "C++"), но если вы не работаете с более чем одним компилятором, это обычно не проблема.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...