Почему «#pragma Once» C/C++ не является стандартом ISO?

StackOverflow https://stackoverflow.com/questions/1695807

  •  18-09-2019
  •  | 
  •  

Вопрос

В настоящее время я работаю над большим проектом, и поддержка всех этих объектов, включая охрану, сводит меня с ума!Писать его от руки — удручающе пустая трата времени.Хотя многие редакторы могут создавать защиту включения, это не сильно помогает:

  1. Редактор генерирует защитный символ на основе имени файла.Проблема возникает, когда у вас есть заголовки с одинаковым именем файла в разных каталогах.Оба они получат одинаковую защиту.Включение структуры каталогов в защитный символ потребует от редактора какого-то причудливого подхода, поскольку косые и обратные косые черты в макросе — не самое лучшее.

  2. Когда мне нужно переименовать файл, я также должен переименовать все включаемые охранники (в комментариях ifndef, define и, в идеале, endif).Раздражающий.

  3. Препроцессор забит тоннами символов, без понятия, что они означают.

  4. Тем не менее, определение включается один раз, компилятор по-прежнему открывает заголовок каждый раз, когда встречает включение заголовка.

  5. Включаемые охранники не вписываются ни в пространства имен, ни в шаблоны.Фактически они подрывают пространства имен!

  6. У вас есть шанс, что ваш символ охраны не будет уникальным.

Возможно, они были приемлемым решением во времена, когда программы содержали менее 1000 заголовков в одном каталоге.Но в наши дни?Он древний и не имеет ничего общего с современными привычками кодирования.Что меня больше всего беспокоит, так это то, что эти проблемы могут быть почти полностью решены с помощью директивы #pragma Once.Почему это не стандарт?

Это было полезно?

Решение

Директива типа #pragma once не так-то просто определить полностью переносимый способ, который имеет явные и недвусмысленные преимущества.Некоторые концепции, в отношении которых возникают вопросы, не четко определены во всех системах, поддерживающих C, и его простое определение может не дать никаких преимуществ по сравнению с обычными средствами защиты включения.

Когда компиляция встречает #pragma once, как ему следует идентифицировать этот файл, чтобы он больше не включал его содержимое?

Очевидный ответ — уникальное расположение файла в системе.Это нормально, если в системе есть уникальные местоположения для всех файлов, но многие системы предоставляют ссылки (символические и жесткие ссылки), которые означают, что «файл» не имеет уникального местоположения.Следует ли повторно включить файл только потому, что он был найден под другим именем?Возможно нет.

Но теперь возникает проблема: как можно определить поведение #pragma once таким образом, который имеет точное значение на всех платформах - даже на тех, у которых даже нет каталогов, не говоря уже о символических ссылках - и при этом обеспечивает желаемое поведение в системах, где они есть?

Можно сказать, что идентичность файла определяется его содержимым, поэтому, если включенный файл имеет #pragma once и включен файл, который имеет точно то же содержание, затем второй и последующие #includes не будет иметь никакого эффекта.

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

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

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

Учитывая потенциальные ловушки и затраты, а также тот факт, что традиционные встроенные ограждения действительно работают, для меня неудивительно, что комитет по стандартизации не почувствовал необходимости стандартизировать #pragma once.

Другие советы

Защита включения определенно раздражает, и C изначально должен был быть разработан таким образом, чтобы заголовки включались один раз по умолчанию, что требовало какой-то специальной опции для включения заголовка несколько раз.

Однако это не так, и вам в основном приходится использовать защиту включения.Тем не менее, #pragma once довольно широко поддерживается, так что вам, возможно, удастся обойтись без него.

Лично я решаю вашу первую проблему (с аналогичным названием «включаемые файлы»), добавляя GUID в защиту включения.Это уродливо, и большинство людей ненавидят это (поэтому мне часто приходится не использовать его на работе), но ваш опыт показывает, что эта идея имеет некоторую ценность - даже если она ужасно уродлива (но опять же, вся эта штука с включением защиты - это своего рода хак - почему бы не пойти на полную катушку?):

#ifndef C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546
#define C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546

// blah blah blah...

#endif

Я слышал, что компиляторы на самом деле не открывают повторно заголовочные файлы, в которых есть защита включения (они научились распознавать эту идиому).Я не уверен, правда ли это (или насколько это правда);Я никогда не измерял это.Меня это тоже не беспокоит, но мои проекты не настолько велики, чтобы это было проблемой.

Мой хак GUID в значительной степени решает пункты 1, 5 и 6.Я просто живу с пунктами 2, 3 и 4.На самом деле, что касается пункта 2, вы можете обойтись без переименования макроса включения защиты при переименовании файла, поскольку GUID гарантирует, что он останется уникальным.На самом деле нет никакой причины включать имя файла в GUID.Но я да - традиция, я полагаю.

  1. Как часто вам приходится добавлять включаемый файл в этот проект?Неужели так сложно добавить DIRNAME_FILENAME в охрану?И всегда есть GUID.
  2. Вы действительно так часто переименовываете файлы?Всегда?Кроме того, добавление GUARD в #endif так же раздражает, как и любой другой бесполезный комментарий.
  3. Я сомневаюсь, что ваши 1000 определений защиты заголовочных файлов составляют хотя бы небольшой процент от количества определений, генерируемых вашими системными библиотеками (особенно в Windows).
  4. Я думаю, что MSC 10 для DOS (более 20 лет назад) отслеживал, какие заголовки были включены, и, если они содержали, средства защиты пропускали их при повторном включении.Это старая технология.
  5. пространства имен и шаблоны не должны охватывать заголовки.Дорогой мой, не говори мне, что ты делаешь это:

    template <typename foo>
    class bar {
    #include "bar_impl.h"
    };
    
  6. Ты это уже сказал.

Как уже отмечалось, стандарт C++ должен учитывать различные платформы разработки, некоторые из которых могут иметь ограничения, делающие невозможным реализацию поддержки #pragma Once.

С другой стороны, поддержка потоков ранее не добавлялась по той же причине, но новый стандарт C++, тем не менее, включает потоки.И в последнем случае мы можем кросс-компилировать для очень ограниченной платформы, но разработка ведется на полноценной платформе.Поскольку GCC поддерживает это расширение, я думаю, что реальный ответ на ваш вопрос заключается в том, что нет заинтересованной стороны во внедрении этой функции в стандарт C++.

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

Я избегаю использования включаемых охранников.Если мне придется перенести свой код в компилятор без поддержки #pragma Once, я напишу сценарий, который добавит защиту включения во все файлы заголовков.

Проблема возникает, когда у вас есть заголовки с одинаковым именем файла в разных каталогах.

Итак, у вас есть два заголовка, которые оба называются ice_cream_maker.h в вашем проекте, оба из которых имеют класс под названием ice_cream_maker в них определено, что выполняет ту же функцию?Или вы звоните каждому классу в вашей системе foo?

Тем не менее, определение включается один раз, компилятор по-прежнему открывает заголовок каждый раз, когда встречает включение заголовка.

Отредактируйте код, чтобы не включать заголовки несколько раз.

Для зависимых заголовков (а не основного заголовка библиотеки) я часто использую защиту заголовков, которая выглядит следующим образом:

#ifdef FOO_BAR_BAZ_H
#error foo_bar_baz.h multiply included
#else
#define FOO_BAR_BAZ_H

// header body

#endif

IIRC, #pragma что-либо не является частью языка.И это во многом проявляется на практике.

(редактировать: полностью согласен с тем, что система включения и связывания должна была быть в центре внимания нового стандарта, поскольку это одна из наиболее очевидных слабых сторон «в наши дни»)

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

Кроме того, #pragma Once уже довольно давно поддерживается как компиляторами MS, так и GCC, так почему вас беспокоит, что он не соответствует стандарту ISO?

Прагматичное решение:

1) выберите некоторую последовательную политику именования охранников (например,путь относительно корня проекта + имя файла или что-то еще по вашему выбору).Включите возможные исключения для стороннего кода.

2) написать программу (подойдет простой скрипт Python), чтобы рекурсивно обходить дерево исходных кодов и проверять, все ли охранники соответствуют политике.И всякий раз, когда охранники ошибаются, выведите diff (или сценарий sed, или что-то еще), который пользователь может легко исправить.Или просто запросите подтверждение и внесите изменения из той же программы.

3) заставить всех участников проекта использовать его (скажем, перед отправкой в ​​систему контроля версий).

Я думаю, что правильный способ разрешить множественное включение только с помощью специальной прагмы и запретить множественное включение по умолчанию например:

#pragma allow_multiple_include_this_file

Итак, раз уж вы спросили, почему.Вы отправили свое предложение стандартным разработчикам?:) Я тоже не отправляю.Может ли это быть причиной?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top