Skip to content

Instantly share code, notes, and snippets.

@Oxore
Last active July 28, 2019 10:51
Show Gist options
  • Save Oxore/7543660534357b316ac2838699b1edbe to your computer and use it in GitHub Desktop.
Save Oxore/7543660534357b316ac2838699b1edbe to your computer and use it in GitHub Desktop.
Заметка про хедеры

Хедеры

Хедеры (header files, headers) существуют, чтобы определить интерфейс доступа к функциональности модулей и даже целых библиотек путём описания прототипов функций, новых типов данных, глобальных переменных и дефиниций.

Хедер не должен содержать компилируемый код

Но давайте представим, что имеется хедер какой-либо библиотеки, в котором объявлена функция с неиспользуемым аргументом, а ваш проект компилируется с помощью GCC/Clang с флагами -Wall -Wextra -Werror и всё падает на тех модулях, которые включают этот злополучный хедер. В итоге из-за сраного хедера вы не можете использовать -Werror вообще, а если вас раздражают предупреждения, то и от -Wextra придётся отказаться.

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

Ситуации с компилируемым кодом в хедерах больше характерны для C++, нежели для C, где короткие методы классов реализуются прям в прототипе класса и содержат код, генерирующий предупреждения. Лично я сталкивался с подобным при работе с симулятором Menge. Да что там спецсофт, даже стандартная библиотека сожержит компилируемый код в хедарах! Это я ещё, оказывается, про шаблоны (templates) не слышал.

Не включайте хедеры в хедеры

Это утверждение уже скорее моё мнение, чем правило хорошего кода. Такой подход практикуется в компиляторе диалекта ANSI C для операционной системы Plan9 - более подробно в статье Роба Пайка (или на web.archive.org если оригинальная ссылка как обычно недоступна). Так же в другой статье про язык го Роб Пайк рассказывает чего можно ожидать от вложенных хедеров с защитой от повторного включения при масштабировании проекта. Хотя в целом однозначную оценку тому или иному подходу дать трудно - исследований на эту тему я не нашёл, но постараюсь объяснить в чём отличия этих двух подходов.

Если вы используете подход с хедерами, включающими зависимые хедеры и обрамлёнными в обязательном порядке защитой от повторного включения, то наверняка у вас есть несколько хедеров, зависящих от какого-то одного более простого и есть ситуации, когда вы используете два или более таких хедеров в одном модуле (модуль, он же translation unit, он же, как правило, .c/.cpp файл, который компилируется в объектник). Таким образом, включая два и более таких хедеров, компилятор будет пару раз или более заглядывать в тот маленький общий хедер, затрачивая на это какие-то ресурсы. Какие это по объёму ресурсы - конкретно сказать невозможно, нужно проводить экспериментальную оценку для различных компиляторов.

Есть и преимущество: если хедер a.h был изменён и больше не зависит от другого более общего хедера b.h, то b.h можно спокойно исключить из a.h, в отличие от другого подхода.

Другой подход (из Plan9) как раз заключается в принципиальном отказе от включения хедеров в хедеры, тем самым обеспечивая только одну попытку включения компилятором каждого упомянутого хедера в рамках компиляции одного модуля. Если a.h зависит от b.h, то b.h должен быть включён где-нибудь перед a.h в модуле. Но в процессе разработки a.h может перестать зависеть от b.h и тогда придётся прочёсывать все модули, включающие a.h и исключать из зависимости модуля файл b.h, предварительно удостоверившись, что от него больше ничего не зависит.

Но для таких ситуаций как раз существует утилита под незамысловатым называнием include-what-you-use, позволяющая отследить и исключить неиспользуемые хедеры. Для обоих подходов она будет полезна.

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

Вместо заключения

Только ситхи всё возводят в абсолют. Иногда лучше использовать компилируемый код прямо в хедерах, например инлайны, но очень осторожно. Включайте хедеры в хедеры если вам кажется это невероятно эффективным и наоборот. Знайте больше, думайте своей головой и трезво оценивайте свои возможности и потребности. Как сказал Анатолий Вассерман (а до него наверняка ещё кто-нибудь), инженерная деятельность - это поиск компромиссов; если удаётся достичь чего-то без компромиссов, то это уже изобретательский прорыв.

@Oxore
Copy link
Author

Oxore commented Jul 28, 2019

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

Хедеры (header files, headers) существуют, чтобы определить ... путём описания ..., глобальных переменных и ...

Ох не стоит помещать глобальные переменные в хедер, ибо будет ошибка линковки типа multiple definition, если хедер включён в больше, чем один модуль (translation unit). Но можно помещать extern прототипы таких переменных.

Хедер не должен содержать компилируемый код

Если только не понадобилось вдруг поместить туда функцию с классом хранения static inline.

Ситуации с компилируемым кодом в хедерах больше характерны для C++

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

Не включайте хедеры в хедеры

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment