Почему C # C ++ не имеет «стандартного стандарта ISO»?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 #ifndef C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546 #define C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546 // blah blah blah... #endif 

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

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

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

     template  class bar { #include "bar_impl.h" }; 
  6. Вы уже это сказали.

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

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

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

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

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

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

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

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

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

 #ifdef FOO_BAR_BAZ_H #error foo_bar_baz.h multiply included #else #define FOO_BAR_BAZ_H // header body #endif 

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

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

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

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

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

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

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

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

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

 #pragma allow_multiple_include_this_file 

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

Interesting Posts
Давайте будем гением компьютера.