C ++ #include семантика
Это несколько вопросов для одной и той же инструкции предварительной обработки.
1 – или “”?
Помимо информации, найденной в MSDN:
Директива #include (C-C ++)
- Как надежно определить Mac OS X, iOS, Linux, Windows в препроцессоре C?
- Как объединить два раза с препроцессором C и развернуть макрос, как в «arg ## _ ## MACRO»?
- Как я могу генерировать уникальные значения в препроцессоре C?
- Существуют ли какие-либо macros, чтобы определить, скомпилирован ли мой код для Windows?
- Как сделать переменный макрос (переменное количество аргументов)
1.a: Каковы различия между двумя обозначениями?
1.b: Все ли компиляторы реализуют их одинаково?
1.c: Когда вы будете использовать , и когда вы будете использовать «» (то есть, каковы критерии, которые вы использовали бы для использования одного или другого для заголовка)?
2 – #include {TheProject / TheHeader.hpp} или {TheHeader.hpp}?
Я видел, как минимум два способа записи include в себя заголовки проектов. Учитывая, что у вас есть как минимум 4 типа заголовков, то есть:
- частные заголовки вашего проекта?
- заголовки вашего проекта, но которые экспортируют символы (и, следовательно, «публичные»)
- заголовки другого проекта, который ваш модуль связывает с
- заголовки компилятора или стандартной библиотеки
Для каждого типа заголовков:
2.a: Вы использовали бы или “”?
2.b: Вы включили бы с помощью {TheProject / TheHeader.hpp} или с помощью {TheHeader.hpp}?
3 – Бонус
3.a: Вы работаете над проектом с источниками и / или заголовками в древовидной организации (т. Е. Каталоги внутри каталогов, в отличие от «каждого файла в одном каталоге») и каковы плюсы и минусы?
- Как избежать символа # в макросе #define?
- Как сделать строку char из значения макроса C?
- Можно ли перебирать аргументы в переменных массивах?
- Выход препроцессора Xcode
- Как я могу увидеть исходный файл C / C ++ после предварительной обработки в Visual Studio?
- Stringification - как это работает?
- Макрос для конкатенации двух строк в C
- Макросы препроцессора с несколькими линиями
Прочитав все ответы, а также документацию компилятора, я решил, что буду следовать следующему стандарту.
Для всех файлов, будь то заголовки проектов или внешние заголовки, всегда используйте шаблон:
#include
Пространство имен, по крайней мере, одного каталога, чтобы избежать столкновения.
Конечно, это означает, что каталог проекта, в котором заголовки проекта должны быть добавлены как «default include header» в make-файл, тоже.
Причиной этого выбора является то, что я нашел следующую информацию:
1. Шаблон include “” является зависимым от компилятора
Я дам ответы ниже
1.a Стандарт
Источник:
- C ++ 14 Рабочий проект n3797: https://isocpp.org/files/papers/N3797.pdf
- C ++ 11, C ++ 98, C99, C89 (цитируемый раздел не изменяется во всех этих стандартах)
В разделе 16.2 «Ввод исходного файла» мы можем прочитать:
Директива предварительной обработки формы
#include
new-line ищет последовательность определённых реализацией мест для заголовка, идентифицированного однозначно указанной последовательностью между разделителями <и> и вызывает замену этой директивы на все содержимое заголовка. Как указано места, или идентифицированный заголовок определяется реализацией.
Это означает, что #include <...> будет искать файл в определенной реализации.
Затем следующий параграф:
Директива предварительной обработки формы
#include "q-char-sequence" new-line
вызывает замену этой директивы всем содержимым исходного файла, идентифицированного указанной последовательностью между «разделителями». Именованный исходный файл выполняется в соответствии с реализацией. Если этот поиск не поддерживается или если поиск не выполняется , директива перерабатывается, как если бы она читалась
#include
new-line с идентичной содержащейся последовательностью (включая> символы, если таковые имеются) из исходной директивы.
Это означает, что #include “…” будет искать файл в определенном порядке реализации, а затем, если файл не будет найден, будет выполняться другой поиск, как если бы он был #include <...>
Вывод состоит в том, что мы должны прочитать документацию компиляторов.
Обратите внимание, что по какой-либо причине нигде в стандартах не делается различий между заголовками «system» или «library» или другими заголовками. Единственное отличие в том, что #include <...> похоже, предназначено для заголовков, а #include “…”, похоже, предназначено для источника (по крайней мере, в английской формулировке).
1.b Visual C ++:
Источник:
#include “MyFile.hpp”
Препроцессор выполняет поиск включенных файлов в следующем порядке:
- В том же каталоге, что и файл, содержащий оператор #include.
- В каталогах любых ранее открытых включенных файлов в обратном порядке, в которых они были открыты. Поиск начинается с каталога включенного файла, который был открыт последним, и продолжается через каталог открытого файла, который был открыт первым.
- По пути, указанному каждым параметром компилятора / I.
- (*) Вдоль путей, заданных переменной среды INCLUDE или включенной по умолчанию средой разработки.
#include
Препроцессор выполняет поиск включенных файлов в следующем порядке:
- По пути, указанному каждым параметром компилятора / I.
- (*) Вдоль путей, заданных переменной среды INCLUDE или включенной по умолчанию средой разработки.
Обратите внимание на последний шаг
В документе не ясно, что «Вдоль путей, заданных переменной среды INCLUDE», для части <...>
и "..."
. Следующая цитата заставляет его придерживаться стандарта:
Для include файлов, которые указаны как #include “path-spec”, поиск каталога начинается с каталога родительского файла, а затем проходит через каталоги любых файлов дедушки и бабушки. То есть поиск начинается относительно каталога, содержащего исходный файл, который содержит директиву #include, которая обрабатывается. Если нет файла grandparent и файл не найден, поиск продолжается, как если бы имя файла было заключено в угловые скобки.
Последний шаг (отмеченный звездочкой) является, таким образом, интерпретацией из чтения всего документа.
1.c g ++
Источник:
- https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
- https://gcc.gnu.org/onlinedocs/cpp/Include-Syntax.html
- https://gcc.gnu.org/onlinedocs/cpp/Include-Operation.html
- https://gcc.gnu.org/onlinedocs/cpp/Invocation.html
- https://gcc.gnu.org/onlinedocs/cpp/Search-Path.html
- https://gcc.gnu.org/onlinedocs/cpp/Once-Only-Headers.html
- https://gcc.gnu.org/onlinedocs/cpp/Wrapper-Headers.html
- https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html
Следующая цитата суммирует процесс:
GCC […] будет искать заголовки, запрошенные с #include
в [системных каталогах] […] Все каталоги, названные именем -I, выполняются в порядке слева направо перед каталогами по умолчанию
GCC ищет заголовки, запрошенные с #include «file» сначала в каталоге, содержащем текущий файл, затем в каталогах, указанных в параметрах -iquote, а затем в тех же местах он искал заголовок, запрошенный с помощью угловых скобок.
#include “MyFile.hpp”
Этот вариант используется для файлов заголовков вашей собственной программы. Препроцессор выполняет поиск включенных файлов в следующем порядке:
- В том же каталоге, что и файл, содержащий оператор #include.
- Вдоль пути, заданного параметром -iquote компилятора.
- Что касается #include
#include
Этот вариант используется для файлов системных заголовков. Препроцессор выполняет поиск включенных файлов в следующем порядке:
- По пути, указанному каждым параметром -I компилятора.
- Внутри системных каталогов.
1.d Oracle / Sun Studio CC
Источник:
Обратите внимание, что текст несколько противоречит (см. Пример для понимания). Ключевая фраза: « Разница заключается в том, что текущий каталог выполняется только для файлов заголовков, имена которых указаны в кавычках ».
#include “MyFile.hpp”
Этот вариант используется для файлов заголовков вашей собственной программы. Препроцессор выполняет поиск включенных файлов в следующем порядке:
- Текущий каталог (то есть каталог, содержащий файл «включая»)
- Каталоги, названные с параметрами -I, если они есть
- Системный каталог (например, каталог / usr / include)
#include
Этот вариант используется для файлов системных заголовков. Препроцессор выполняет поиск включенных файлов в следующем порядке:
- Каталоги, названные с параметрами -I, если они есть
- Системный каталог (например, каталог / usr / include)
1.e Справочник компилятора XL C / C ++ – IBM / AIX
Источник:
- http://www.bluefern.canterbury.ac.nz/ucsc%20userdocs/forucscwebsite/c/aix/compiler.pdf
- http://www-01.ibm.com/support/docview.wss?uid=swg27024204&aid=1
Оба документа называются «Справочник компилятора XL C / C ++». Первый документ старше (8,0), но его легче понять. Второй – более новый (12.1), но немного сложнее расшифровать.
#include “MyFile.hpp”
Этот вариант используется для файлов заголовков вашей собственной программы. Препроцессор выполняет поиск включенных файлов в следующем порядке:
- Текущий каталог (то есть каталог, содержащий файл «включая»)
- Каталоги, названные с параметрами -I, если они есть
- Системный каталог (например, директории / usr / vac [cpp] / include или / usr / include)
#include
Этот вариант используется для файлов системных заголовков. Препроцессор выполняет поиск включенных файлов в следующем порядке:
- Каталоги, названные с параметрами -I, если они есть
- Системный каталог (например, каталог / usr / vac [cpp] / include или / usr / include)
1.e Заключение
Шаблон “” может привести к точной ошибке компиляции компиляторов, и поскольку в настоящее время я работаю как на Windows Visual C ++, Linux g ++, Oracle / Solaris CC и AIX XL, это неприемлемо.
В любом случае, преимущество «описанных функций» далеко не интересно, так что …
2. Используйте шаблон {namespace} /header.hpp
Я видел на работе ( т. Е. Это не теория, это реальный, болезненный профессиональный опыт ) два заголовка с таким же именем, один в локальном каталоге проекта, а другой в глобальном включении.
Поскольку мы использовали шаблон «», и этот файл был включен как в локальные заголовки, так и в глобальные заголовки, не было никакого способа понять, что действительно происходит, когда появились странные ошибки.
Использование каталога в include могло бы сэкономить время, потому что пользователю пришлось бы либо написать:
#include
или
#include
Вы заметите, что пока
#include "Header.hpp"
было бы успешно скомпилировано, тем самым, все еще скрывая проблему, тогда как
#include
не собирались бы в обычных условиях.
Таким образом, приклеивание к нотации <> сделало бы обязательным для разработчика префикс include с правильным каталогом, еще одна причина предпочесть <> to “”.
3. Заключение
Используя как обозначение <>, так и именное пространство вместе удаляют из предварительного компилятора возможность угадывать файлы, вместо этого ищут только каталоги включенных по умолчанию.
Конечно, стандартные библиотеки по-прежнему включены как обычно, то есть:
#include #include
Обычно я использую <> для заголовков системы и “” для заголовков проектов. Что касается путей, это необходимо только в том случае, если файл, который вы хотите, находится в подкаталоге пути включения.
например, если вам нужен файл в / usr / include / SDL /, но только / usr / include / находится в вашем пути включения, то вы можете просто использовать:
#include
Кроме того, имейте в виду, что если путь, который вы положили, начинается с /, это относится к текущему рабочему каталогу.
EDIT TO ANSWER COMMENT: Это зависит, если в библиотеке есть только несколько включений, я бы включил его подкаталог в путь include, но если в библиотеке много заголовков (например, десятки), я бы предпочел просто это в subdir, который я указываю. Хорошим примером этого являются системные заголовки Linux. Вы используете их как:
#include #include
и т.п.
ИЗМЕНИТЬ, ЧТОБЫ ВКЛЮЧАТЬ ДРУГОЙ ОТВЕТ: также, если возможно, что две или более библиотеки предоставляют заголовки с тем же именем, то решение подкаталога в основном дает каждому заголовку пространство имен.
Чтобы процитировать из стандарта C99 (с первого взгляда формулировка кажется идентичной в стандарте C90, но я не могу вырезать-n-paste из этого):
Директива предварительной обработки формы
# include "q-char-sequence" new-line
вызывает замену этой директивы всем содержимым исходного файла, идентифицированного указанной последовательностью между «разделителями». Именованный исходный файл выполняется в соответствии с реализацией. Если этот поиск не поддерживается или если поиск не выполняется , директива перерабатывается, как если бы она читалась
# include
new-line с идентичной содержащейся последовательностью (включая> символы, если таковые имеются) из исходной директивы.
Таким образом, места, которые искали: #include "whatever"
являются супер-множеством местоположений, поиск которых осуществляется с помощью #include
. objective состоит в том, что первый стиль будет использоваться для заголовков, которые в целом «принадлежат» вам, а второй метод будет использоваться для заголовков, которые «принадлежат» компилятору / среде. Конечно, часто есть серые области, которые вы должны использовать для заголовков Boost, например? Я бы использовал #include <>
, но я бы не стал спорить слишком много, если бы кто-то из моей команды захотел #include ""
.
На практике я не думаю, что кто-то уделяет много внимания тому, какая форма используется, пока assembly не сломается. Я, конечно, не помню, чтобы это упоминалось в обзоре кода (или, в противном случае, даже).
Я займу вторую часть вашего вопроса:
Обычно я использую
когда я
заголовки третьей стороны. И "myHeader.h"
при включении заголовков из проекта.
Причина, по которой я использую
вместо
заключается в том, что возможно, что более одной библиотеки имеет файл «libHeader.h». Чтобы включить их, вам нужно имя библиотеки как часть включенного имени файла.
1.a: Каковы различия между двумя обозначениями?
“” начинает поиск в каталоге, где находится файл C / C ++. <> запускает поиск в каталогах -I и по умолчанию (например, / usr / include). Оба они, в конечном счете, ищут один и тот же набор мест, только порядок отличается.
1.b: Все ли компиляторы реализуют их одинаково?
Надеюсь, что так, но я не уверен.
1.c: Когда вы будете использовать <>, и когда вы будете использовать «» (то есть, каковы критерии, которые вы использовали бы для использования одного или другого для заголовка)?
Я использую «», когда включенный файл должен находиться рядом с файлом C, <> во всех остальных случаях. В частности, в нашем проекте все «общедоступные» включают файлы в каталог project / include, поэтому я использую для них <>.
2 – #include {TheProject / TheHeader.hpp} или {TheHeader.hpp}?
Как уже указывалось, xxx / filename.h позволяет делать такие вещи, как diskio / ErrorCodes.h и netio / ErrorCodes.h
* частные заголовки вашего проекта?
Частный заголовок моей подсистемы в проекте. Используйте «filename.h» Общий заголовок моей подсистемы в проекте (не видимый вне проекта, но ansible для других подсистем). Используйте или, в зависимости от соглашения, адаптированного для проекта. Я предпочел бы использовать
* заголовки вашего проекта, но которые экспортируют символы (и, следовательно, «публичные»)
включите их точно так же, как пользователи вашей библиотеки. Вероятно
* заголовки другого проекта, который ваш модуль связывает с
Определяется проектом, но, конечно, используя <> * заголовки компилятора или стандартной библиотеки Определенно <>, в соответствии со стандартом.
3.a: Вы работаете над проектом с источниками и / или заголовками в древовидной организации (т. Е. Каталоги внутри каталогов, в отличие от «каждого файла в одном каталоге») и каковы плюсы и минусы?
Я работаю над структурированным проектом. Как только у вас будет больше, чем количество файлов, некоторое разделение станет очевидным. Вы должны пойти так, как код тянет вас.
Если я правильно помню.
Вы используете бриллиант для всех библиотек, которые можно найти в вашем «пути». Таким образом, любая библиотека, находящаяся в STL, или те, которые вы установили. В Linux ваш путь обычно «/ usr / include», в Windows я не уверен, но я бы предположил, что он находится под «C: \ windows».
Вы используете «», чтобы указать все остальное. «my_bla.cpp» без информации о начальном каталоге будет разрешен в каталог, в котором находится ваш код / компиляция, или вы также можете указать точное местоположение вашего объекта с ним. Подобно этому “c: \ myproj \ some_code.cpp”
Тип заголовка не имеет значения, просто местоположение.
Re <> vs “”. В моем магазине я очень разбираюсь в вопросах «стиля». Одна из немногих областей, где у меня есть требование, заключается в использовании угловых скобок в операторах #include – это правило: если вы # включаете в себя операционную систему или файл компилятора, вы можете использовать угловые скобки, если это необходимо. Во всех остальных случаях они запрещены. Если вы # включаете файл, написанный кем-то здесь или сторонней библиотекой, <> запрещен.
Причина такова: #include “xh” и #include не искать одни и те же пути. #include будет искать только системный путь и все, что у вас есть. Важно, что он не будет искать путь, где находится файл xh, если этот каталог не включен в путь поиска каким-либо другим способом.
Например, предположим, что у вас есть следующие файлы:
C: \ DEV \ углы \ main.cpp
#include "c:\utils\mylib\mylibrary.h" int main() { return 0; }
C: \ Drivers \ MyLib \ mylibrary.h
#ifndef MYLIB_H #define MYLIB_H #include namespace mylib { void Speak(SpeechType speechType); }; #endif
C: \ Drivers \ mhlib \ speech.h
#ifndef SPEECH_H #define SPEECH_H namespace mylib { enum SpeechType {Bark, Growl}; }; #endif
Это не будет компилироваться без изменения пути либо путем установки переменной среды PATH, либо -i’ing в каталоге c: \ utils \ mhlib \. Компилятор не сможет переустановить #include
хотя этот файл находится в том же каталоге, что и mylibrary.h
!
Мы широко используем относительные и абсолютные пути в операторах #include в нашем коде по двум причинам.
1) Сохраняя библиотеки и компоненты из основного исходного дерева (т. Е. Помещая библиотеки утилиты в специальный каталог), мы не связываем жизненный цикл библиотеки с жизненным циклом приложения. Это особенно важно, когда у вас есть несколько различных продуктов, которые используют общие библиотеки.
2) Мы используем Junctions для сопоставления физического местоположения на жестком диске с каталогом на логическом диске, а затем используем полностью определенный путь на логическом диске во всех #includes. Например:
#include "x:\utils\mylib.h"
– хорошо, x: является подключенным диском, а x: \ utils указывает на c: \ code \ utils_1.0 на жестком диске
#include "c:\utils_1.0\mylib.h"
– плохо! приложение tha t # включает mylib.h теперь связано с конкретной версией библиотеки MYLIB, и все разработчики должны иметь ее в том же каталоге на своем жестком диске c: \ utils_1.0
Наконец, широкая, но трудно достижимая цель моей команды состоит в том, чтобы иметь возможность поддерживать компиляцию с одним кликом. Это включает в себя возможность скомпилировать основное исходное дерево, делая не что иное, как получение кода из исходного элемента управления, а затем попадание «компиляции». В частности, я отказываюсь устанавливать пути и машинные каталоги #include для того, чтобы их можно было компилировать, потому что каждый маленький дополнительный шаг, который вы добавляете к фазе настройки в buildign для машины разработки, просто усложняет работу, и требуется больше времени, чтобы получить новую машину для ускорения и генерации кода.
Существуют два основных различия между <>
и ""
. Первый – это символ, который закончит имя – в именах заголовков нет escape-последовательностей, и поэтому вы можете принудительно выполнить #include
или "bla>file.cpp"
. Это, вероятно, не будет Однако другое отличие заключается в том, что система не должна встречаться на ""
, просто <>
. Таким образом, #include "iostream"
не гарантированно работает; #include
is. Мои личные предпочтения использовать ""
для файлов, которые являются частью проекта, и <>
для файлов, которые не являются. Некоторые люди используют только <>
для стандартных заголовков библиотек и ""
для всех остальных. Некоторые люди даже используют <>
только для Boost и std, это зависит от проекта. Как и все аспекты стиля, самое главное – быть последовательным.
Что касается пути, внешняя библиотека укажет соглашение для заголовков; например
. В локальном проекте я бы написал все пути относительно srcdir верхнего уровня (или в проекте библиотеки, где они разные, каталог include).
При написании библиотеки вы также можете использовать <> для разделения между частными и публичными заголовками или не -I
исходный каталог, но каталог выше, поэтому вы #include "public_header.hpp"
и "src/private_header.hpp"
. Это действительно зависит от вас.
EDIT: Что касается проектов со структурами каталогов, я бы очень рекомендовал их. Представьте себе, если все импульсы были в одном каталоге (и не было подпространств)! Структура каталогов хороша тем, что позволяет вам находить файлы проще, и это позволяет вам больше гибкости в именовании ( "module\_text\_processor.hpp"
в отличие от "module/text\_processor.hpp"
). Последнее более естественно и проще в использовании.
Я использую <...> из системного файла заголовка (stdio, iostreams, string и т. Д.) И «…» для заголовков, специфичных для этого проекта.
Мы используем #include “header.h” для заголовков, локальных для проекта, и #include для включения системы, сторонних включений и других проектов в решении. Мы используем Visual Studio, и гораздо проще использовать каталог проекта в заголовке include, таким образом, всякий раз, когда мы создаем новый проект, нам нужно указывать путь include для каталога, содержащего все каталоги проектов, а не отдельный путь для каждый проект.