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

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

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

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

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

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

  5. Include guards не вписываются в пространства имен и шаблоны. На самом деле они подрывают пространства!

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

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

9 ответов


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

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

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

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

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

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

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

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

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


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

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

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

#ifndef C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546
#define C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546

// blah blah blah...

#endif

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

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


  1. Как часто вам нужно добавлять файл include в этот проект? Действительно ли так сложно добавить dirname_filename в guard? И всегда есть GUIDs.
  2. вы действительно переименовываете файлы так часто? Когда-нибудь? Кроме того, размещение охранника в #endif так же раздражает, как и любой другой бесполезный комментарий.
  3. Я сомневаюсь, что ваши 1000 заголовочных файлов guard определяют даже небольшой процент от количества определений, генерируемых вашими системными библиотеками (особенно на Windows.)
  4. Я думаю, что MSC 10 для DOS (20+ лет назад) отслеживал, какие заголовки были включены, и если бы они содержали охранников, пропустил бы их, если бы они были включены снова. Это старая техника.
  5. пространства имен и шаблоны не должны охватывать заголовки. Боже мой, только не говорите мне, что вы это делаете!--12-->

    template <typename foo>
    class bar {
    #include "bar_impl.h"
    };
    
  6. ты это уже говорил.


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

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

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

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


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

Итак, у вас есть два заголовка, которые называются 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 ничего не является частью языка. И это часто проявляется на практике.

(edit: полностью согласен с тем, что система включения и связывания должна была быть больше ориентирована на новый стандарт, поскольку это одна из самых очевидных слабостей в "le this day and age")


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

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


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

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

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

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


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

#pragma allow_multiple_include_this_file

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