Почему компилятор не может избежать импорта файла заголовка дважды?

13

Новое в C ++! Итак, я читал это: http://www.learncpp.com/cpp-tutorial/110-a-first-look-at-the-preprocessor/

Заголовок охранников

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

Поэтому мы делаем директивы препроцессора, чтобы избежать этого. Но я не уверен - почему компилятор не может просто ... не импортировать одно и то же дважды?

Учитывая, что средства защиты заголовков являются необязательными (но, видимо, хорошей практикой), это почти заставляет меня думать, что существуют сценарии, когда вы хотите импортировать что-то дважды. Хотя я не могу думать ни о каком таком сценарии вообще. Есть идеи?

Омега
источник
На компиляторе MS есть, #pragma onceкоторый говорит компилятору включать этот файл только один раз.
CodesInChaos

Ответы:

27

Они могут, как показывают новые языки, которые делают.

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

Поскольку C ++ наследует способ обработки заголовочных файлов от C, он поддерживает те же методы. Мы поддерживаем старое дизайнерское решение. Но менять способ работы слишком рискованно, поэтому большой объем кода может сломаться. Так что теперь мы должны научить новых пользователей языка, как использовать охранники.

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

Мартин Йорк
источник
7

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

Одна вещь, которая приходит мне в голову, это то, что у меня был проект, который был API. У меня было два заголовочных файла x86lib.hи x86lib_internal.h. Поскольку внутренняя часть была огромной, я разделил «публичные» биты на x86lib.h, чтобы пользователям не пришлось выделять дополнительное время для компиляции.

Это породило забавную проблему с зависимостями, поэтому у меня получился такой поток в x86lib_internal

  1. Установить ВНУТРЕННИЙ препроцессор
  2. Включите x86lib.h (который был умным, чтобы действовать определенным образом, когда был определен внутренний)
  3. Сделайте некоторые вещи и представьте некоторые вещи, используемые в x86lib.h
  4. Установить ПОСЛЕ определения препроцессора
  5. Снова включите x86lib.h (на этот раз он проигнорирует все, кроме отдельной части AFTER, которая зависит от элементов x86lib_internal

Я бы не сказал, что это был лучший способ добиться этого, но он достиг того, чего я хотел.

Earlz
источник
0

Одна проблема с автоматическим исключением дублирующихся заголовков состоит в том, что стандарт C относительно не говорит о том, что означают имена файлов. Например, предположим, что основной компилируемый файл содержит директивы #include "f1.h"и #include "f2.h", и файлы, найденные для этих директив, содержат #include "f3.h". Если f1.hи f2.hнаходятся в разных каталогах, но были найдены путем поиска путей включения, то было бы неясно , какие #includeдирективы в этих файлах предназначены для загрузки одного и того же f3.hфайла или разных.

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

Если #pragma onceдиректива позволила идентификатору следовать once, с семантикой, которую компилятор должен пропускать файл, если идентификатор совпадает с идентификатором из ранее встречавшейся #pragma onceдирективы, то семантика была бы однозначной; компилятор, который мог бы сказать, что #includeдиректива загрузит тот же #pragma onceфайл с тегами, что и предыдущий, он мог бы сэкономить немного времени, пропуская файл, не открывая его снова, но такое обнаружение не было бы семантически важным, поскольку файл будет пропущен или не имя файла было распознано как совпадение. Однако я не знаю ни о каких компиляторах, работающих таким образом. Наличие компилятора, наблюдающего, соответствует ли файл шаблону, #ifndef someIdentifier / #define someIdentifier / #endif [for that ifndef] / nothing followingи рассматривающего такую ​​вещь как эквивалент вышеупомянутого, #pragma once someIdentifierеслиsomeIdentifier остается определенным, по существу, так же хорошо.

Supercat
источник