Компилируются ли h файлы
Вряд ли опытные C++ разработчики найдут что-нибудь новое и интересное в этой статье. Максимум банальную команду
gcc -c -x c++ -I ./ */*/*/*/*.h
которую они и так знают.
А вот если Вы — разработчик начинающий, или только в первый раз строите документацию по своему проекту, или пробовали это однажды, но увидев тот бред, что сгенерировал doxygen, удалили его и забыли как страшный сон, добро пожаловать под кат, скорее всего Вы найдете дальше парочку полезных мыслей.
Введение
Программирование на языке C++ — это в первую очередь анализ существующего кода.
Вот уже несколько лет я принимаю участие в разработке системы имитационного моделирования. Разработка ведется на великом и могучем… детище Страуструпа. И, надо сказать, сей проект уже давно пишется не столько на C++, сколько на собственном диалекте из макросов (ниже станет понятно, почему это важно). Думаю ситуация такая знакома многим C++ девелоперам.
Когда я только начинал изучать этот проект, мне сразу посоветовали построить по нему документацию с помощью doxygen’а. Но по каким-то причинам дружба с этим инструментом не заладилась, и вспомнил в следующий раз я о нем уже спустя годы.
Предыстория
Время шло, мой профессионализм (как я надеюсь) рос, проект развивался и захотелось мне навести порядок в библиотеке, которую активно правил. Под порядком здесь подразумевается концепция один заголовочный файл = одна сущность (или несколько небольших и тесно связанных), а также разделение всех файлов на три типа — заголовочные, внутренние и исходными — соответственно, *.h, *.inl, *.cpp. Причем разделение должно пройти таким образом, чтобы в заголовочных файлах не осталось определения ни одной функции-члена класса, а все они были либо в cpp-файлах, либо в inl-файлах. Помимо этого весь код модуля должен был быть отформатирован по единому стандарту, и, что самое интересное, большинство сущностей должны были быть откомментированны с использованием специальных команд doxygen’а (\interface, \class, \todo, \brief, \enum и прочие).
Проблема
Сказано — сделано. Спустя примерно несколько недель убитых вечеров безумная задача выполнена! Код красив так, что чуть слеза не наворачивается.
И настало время откинуться на спинку стула в ожидании, пока doxygen построит мне красивейшую и правильнейшую документацию с описанием моего (на тот момент уже самого любимого) модуля системы. И вот с замиранием сердца
cd Project/doc
doxygen project-doxyfile
cd html/
./index.html
Однако то, что предстало моему взору было, мягко говоря, фигней. Doxygen откровенно схалтурил: диаграммы неполные, namespace’ыпустые, макросы мне пытается за функции выдать, в общем всего не перечесть… Но первичный анализ показал, что он не понял макросы (все namespace’ы, умные указатели (собственного производства, кстати) и многое другое было задано с помощью макросов).
Решение в опции PREDEFINED?
Под подозрение в первую очередь попали настройки doxygen’а. Были перепроверены такие опции как ENABLE_PREPROCESSING, MACRO_EXPANSION, EXPAND_ONLY_PREDEF, SEARCH_INCLUDES и INCLUDE_PATH. Но настройки выглядели логично, а макросы правильно восприниматься не стали. Тогда в ход пошла опция PREDEFINED, хотя она и не отвечала на вопрос “почему doxygen лажает?”, но позволила ему объяснить нужные макросы. Это и стало решением проблемы на некоторое время. Но тот факт, что нужно было постоянно дописывать в эту опцию все новые макросы, весьма удручал и заставил продолжить изучение doxygen’а.
Проблема в коде!
Долго думал я над поведением doxygen’а и гуглил. А однажды даже слил его себе по svn с праведной мыслью найти и пофиксить страшный баг, мешающий ему обрабатывать макросы :)
А вот с doxygen’ом это не проходит — заголовочный файл анализируется как самостоятельный, самодостаточный документ с исходным кодом. И если в нем не хватает объявлений используемых сущностей (которые появляются в контексте использования данного заголовочного файла), то doxygen будет ошибаться. И очень похоже, что именно эта болезнь постигла наш проект.
Решение — “компиляции заголовочных файлов”
И, в общем-то, еще, наверняка, можно кое-что улучшить, например, прикрутить сюда cmake, но основная мысль данного поста именно в необходимости “компиляции заголовочных файлов” для борьбы с, казалось бы, странным поведением doxygen’а. Так что на этом остановлюсь, пока не утомил последнего самого усидчивого читателя ;)
Вместо выводов
И собственно, почему пост называется “… или документация на халяву”.
Да просто в коде может не быть ни одной строчки doxygen’овского комментария, но все равно можно построить по нему документацию со всевозможными диаграммами, что может очень помочь при изучении нового проекта. А именно автоматическое построение документации (с удобной навигацией и диаграммами) должно было облегчить новым студентам (а все это происходит в техническом ВУЗе) изучение весьма большой и сложной системы и являлось первоначальной целью использования doxygen в нашем проекте.
Естественно, в комментариях жду конструктивную критику, исправления, дополнения моих в меру кривых команд и вопросы.
Здравствуйте, physic, Вы писали:
P>как можно назвать обработку и подклучение *.h файла в C++ проекте?
Дело в том что *.h файл подключается к *.cpp или *.h файлу препроцессором, а вот когда у компилятора есть полностью скомпонованная единица трансляции *.cpp + все необходимые *.h файлы, тогда компилятор компилит весь получившийся текст. Отсюда можно заключить что *.h файл компилится после обработки препроцессора.
Здравствуйте, physic, Вы писали:
P>как можно назвать обработку и подклучение *.h файла в C++ проекте?
Здравствуйте, Alxndr, Вы писали:
A>Здравствуйте, physic, Вы писали:
P>>как можно назвать обработку и подклучение *.h файла в C++ проекте?
A>Препроцессингом?
Здравствуйте, Alxndr, Вы писали:
A>Здравствуйте, physic, Вы писали:
P>>как можно назвать обработку и подклучение *.h файла в C++ проекте?
A>Препроцессингом?
В .h объявляются ф-ии, т.д. Возможна реализация, но не инициализация. Таким образом, всё что будет необходимо твоему *.c будет взято из *.h, остальное игнорируется. Определение включению заголовочных файлов не скажу, не задавался таким вопросом, раньше не где ни встречал.
*.h файлы компилятором не обрабатываются.
Обрабатываются *.c (*.cpp и т.п.) файлы.
Включение (рекурсивное) инклюдов выполняется при препроцессинге.
Здравствуйте, Alxndr, Вы писали:
A>*.h файлы компилятором не обрабатываются.
Вот тут я не согласен . В *.h файле те же объявления которые могли бы быть и в *.сpp файле, и эти объявления компилируются в бинарный код.
Здравствуйте, physic, Вы писали:
P>Вот тут я не согласен . В *.h файле те же объявления которые могли бы быть и в *.сpp файле, и эти объявления компилируются в бинарный код.
Ачто делает этот год?
Здравствуйте, physic, Вы писали:
P>Здравствуйте, Alxndr, Вы писали:
A>>*.h файлы компилятором не обрабатываются.
P>Вот тут я не согласен . В *.h файле те же объявления которые могли бы быть и в *.сpp файле, и эти объявления компилируются в бинарный код.
После фазы препроцессинга иммем единицу трансляции следующего содержания:
Кури, вообщем, Стандарт и книги.
Здравствуйте, physic, Вы писали:
P>Здравствуйте, physic, Вы писали:
P>>как можно назвать обработку и подклучение *.h файла в C++ проекте?
P>Дело в том что *.h файл подключается к *.cpp или *.h файлу препроцессором, а вот когда у компилятора есть полностью скомпонованная единица трансляции *.cpp + все необходимые *.h файлы, тогда компилятор компилит весь получившийся текст. Отсюда можно заключить что *.h файл компилится после обработки препроцессора.
Путаница в терминологии
Компилятор компилирует единицы трансляции.
В системах, где исходные тексты хранятся в файлах, единица трансляции — это файл, обработанный препроцессорос с рекурсивно включенными *.h-файлами.
Здравствуйте, physic, Вы писали:
P>Вот тут я не согласен . В *.h файле те же объявления которые могли бы быть и в *.сpp файле, и эти объявления компилируются в бинарный код.
При чем здесь объявления? .
Компилятор строит свои внутренние таблицы: функция, параметр, возвращаемое значение; а более что ещё можно сделать?
Потом эти таблицы не трогаются если *.h не меняется. А если меняется, то таблица перестраивается, и компилятся все файлы куда он входил. Что-то вроде этого. Что за код может сгенириться из объявлений?
Crackjack wrote:
> A>Препроцессингом?
>
> В .h объявляются ф-ии, т.д. Возможна реализация, но не инициализация.
Там возможно всё что угодно. Правда в некоторых случаях (когда один h включается из нескольких cpp) программа не слинкуется.
> Таким образом, всё что будет необходимо твоему *.c будет взято из *.h,
> остальное игнорируется. Определение включению заголовочных файлов не
> скажу, не задавался таким вопросом, раньше не где ни встречал.
Обработка .h файлов идёт до компиляции. Компилятор вообще ничего про файлы не знает.
.cpp и .h файлы — чистая условность. Просто так договорились, языку до этого пофиг. Называй файлы как хочешь и что
угодно куда угодно подключай.
именно
Плюс можно вспомнить про precompiled header(msdn). Он применяется как раз для того, чтобы не компилировать многократно одни и те же куски кода, и представляет собой the state of compilation at a certain point.
Здравствуйте, physic, Вы писали:
P>как можно назвать обработку и подклучение *.h файла в C++ проекте?
Здравствуйте, Crackjack, Вы писали:
P>>>как можно назвать обработку и подклучение *.h файла в C++ проекте?
A>>Препроцессингом?
C>В .h объявляются ф-ии, т.д. Возможна реализация, но не инициализация. Таким образом, всё что будет необходимо твоему *.c будет взято из *.h, остальное игнорируется. Определение включению заголовочных файлов не скажу, не задавался таким вопросом, раньше не где ни встречал.
Здравствуйте, physic, Вы писали:
P>как можно назвать обработку и подклучение *.h файла в C++ проекте?
Прикольно реализовано в Boost.Test, там реализация лежит в *.ipp которая инклюдиться в файл, в первый раз такое вижу.
Здравствуйте, Chipsеt, Вы писали:
C>Прикольно реализовано в Boost.Test, там реализация лежит в *.ipp которая инклюдиться в файл, в первый раз такое вижу.
Эта реализация совершенно случайно не генерируется автоматом? (просто гипотеза, нет времени сейчас посмотреть).
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, Alxndr, Вы писали:
A>Здравствуйте, Chipsеt, Вы писали:
C>>Прикольно реализовано в Boost.Test, там реализация лежит в *.ipp которая инклюдиться в файл, в первый раз такое вижу.
A>Эта реализация совершенно случайно не генерируется автоматом? (просто гипотеза, нет времени сейчас посмотреть).
Здравствуйте, physic, Вы писали:
P>как можно назвать обработку и подклучение *.h файла в C++ проекте?
Здравствуйте, Alxndr, Вы писали:
A>Здравствуйте, physic, Вы писали:
P>>как можно назвать обработку и подклучение *.h файла в C++ проекте?
A>Препроцессингом?
Здравствуйте, Alxndr, Вы писали:
A>Здравствуйте, physic, Вы писали:
P>>как можно назвать обработку и подклучение *.h файла в C++ проекте?
A>Препроцессингом?
В .h объявляются ф-ии, т.д. Возможна реализация, но не инициализация. Таким образом, всё что будет необходимо твоему *.c будет взято из *.h, остальное игнорируется. Определение включению заголовочных файлов не скажу, не задавался таким вопросом, раньше не где ни встречал.
*.h файлы компилятором не обрабатываются.
Обрабатываются *.c (*.cpp и т.п.) файлы.
Включение (рекурсивное) инклюдов выполняется при препроцессинге.
Здравствуйте, physic, Вы писали:
P>как можно назвать обработку и подклучение *.h файла в C++ проекте?
Дело в том что *.h файл подключается к *.cpp или *.h файлу препроцессором, а вот когда у компилятора есть полностью скомпонованная единица трансляции *.cpp + все необходимые *.h файлы, тогда компилятор компилит весь получившийся текст. Отсюда можно заключить что *.h файл компилится после обработки препроцессора.
Здравствуйте, Alxndr, Вы писали:
A>*.h файлы компилятором не обрабатываются.
Вот тут я не согласен . В *.h файле те же объявления которые могли бы быть и в *.сpp файле, и эти объявления компилируются в бинарный код.
Здравствуйте, physic, Вы писали:
P>Вот тут я не согласен . В *.h файле те же объявления которые могли бы быть и в *.сpp файле, и эти объявления компилируются в бинарный код.
Ачто делает этот год?
Здравствуйте, physic, Вы писали:
P>Здравствуйте, Alxndr, Вы писали:
A>>*.h файлы компилятором не обрабатываются.
P>Вот тут я не согласен . В *.h файле те же объявления которые могли бы быть и в *.сpp файле, и эти объявления компилируются в бинарный код.
После фазы препроцессинга иммем единицу трансляции следующего содержания:
Кури, вообщем, Стандарт и книги.
Здравствуйте, physic, Вы писали:
P>Здравствуйте, physic, Вы писали:
P>>как можно назвать обработку и подклучение *.h файла в C++ проекте?
P>Дело в том что *.h файл подключается к *.cpp или *.h файлу препроцессором, а вот когда у компилятора есть полностью скомпонованная единица трансляции *.cpp + все необходимые *.h файлы, тогда компилятор компилит весь получившийся текст. Отсюда можно заключить что *.h файл компилится после обработки препроцессора.
Путаница в терминологии
Компилятор компилирует единицы трансляции.
В системах, где исходные тексты хранятся в файлах, единица трансляции — это файл, обработанный препроцессорос с рекурсивно включенными *.h-файлами.
Здравствуйте, physic, Вы писали:
P>Вот тут я не согласен . В *.h файле те же объявления которые могли бы быть и в *.сpp файле, и эти объявления компилируются в бинарный код.
При чем здесь объявления? .
Компилятор строит свои внутренние таблицы: функция, параметр, возвращаемое значение; а более что ещё можно сделать?
Потом эти таблицы не трогаются если *.h не меняется. А если меняется, то таблица перестраивается, и компилятся все файлы куда он входил. Что-то вроде этого. Что за код может сгенириться из объявлений?
Crackjack wrote:
> A>Препроцессингом?
>
> В .h объявляются ф-ии, т.д. Возможна реализация, но не инициализация.
Там возможно всё что угодно. Правда в некоторых случаях (когда один h включается из нескольких cpp) программа не слинкуется.
> Таким образом, всё что будет необходимо твоему *.c будет взято из *.h,
> остальное игнорируется. Определение включению заголовочных файлов не
> скажу, не задавался таким вопросом, раньше не где ни встречал.
Обработка .h файлов идёт до компиляции. Компилятор вообще ничего про файлы не знает.
.cpp и .h файлы — чистая условность. Просто так договорились, языку до этого пофиг. Называй файлы как хочешь и что
угодно куда угодно подключай.
именно
Плюс можно вспомнить про precompiled header(msdn). Он применяется как раз для того, чтобы не компилировать многократно одни и те же куски кода, и представляет собой the state of compilation at a certain point.
Здравствуйте, physic, Вы писали:
P>как можно назвать обработку и подклучение *.h файла в C++ проекте?
Здравствуйте, Crackjack, Вы писали:
P>>>как можно назвать обработку и подклучение *.h файла в C++ проекте?
A>>Препроцессингом?
C>В .h объявляются ф-ии, т.д. Возможна реализация, но не инициализация. Таким образом, всё что будет необходимо твоему *.c будет взято из *.h, остальное игнорируется. Определение включению заголовочных файлов не скажу, не задавался таким вопросом, раньше не где ни встречал.
Здравствуйте, physic, Вы писали:
P>как можно назвать обработку и подклучение *.h файла в C++ проекте?
Прикольно реализовано в Boost.Test, там реализация лежит в *.ipp которая инклюдиться в файл, в первый раз такое вижу.
Здравствуйте, Chipsеt, Вы писали:
C>Прикольно реализовано в Boost.Test, там реализация лежит в *.ipp которая инклюдиться в файл, в первый раз такое вижу.
Эта реализация совершенно случайно не генерируется автоматом? (просто гипотеза, нет времени сейчас посмотреть).
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, Alxndr, Вы писали:
A>Здравствуйте, Chipsеt, Вы писали:
C>>Прикольно реализовано в Boost.Test, там реализация лежит в *.ipp которая инклюдиться в файл, в первый раз такое вижу.
A>Эта реализация совершенно случайно не генерируется автоматом? (просто гипотеза, нет времени сейчас посмотреть).
Здравствуйте, physic, Вы писали:
P>как можно назвать обработку и подклучение *.h файла в C++ проекте?
Здравствуйте, Alxndr, Вы писали:
A>Здравствуйте, physic, Вы писали:
P>>как можно назвать обработку и подклучение *.h файла в C++ проекте?
A>Препроцессингом?
Здравствуйте, Alxndr, Вы писали:
A>Здравствуйте, physic, Вы писали:
P>>как можно назвать обработку и подклучение *.h файла в C++ проекте?
A>Препроцессингом?
В .h объявляются ф-ии, т.д. Возможна реализация, но не инициализация. Таким образом, всё что будет необходимо твоему *.c будет взято из *.h, остальное игнорируется. Определение включению заголовочных файлов не скажу, не задавался таким вопросом, раньше не где ни встречал.
*.h файлы компилятором не обрабатываются.
Обрабатываются *.c (*.cpp и т.п.) файлы.
Включение (рекурсивное) инклюдов выполняется при препроцессинге.
Здравствуйте, physic, Вы писали:
P>как можно назвать обработку и подклучение *.h файла в C++ проекте?
Дело в том что *.h файл подключается к *.cpp или *.h файлу препроцессором, а вот когда у компилятора есть полностью скомпонованная единица трансляции *.cpp + все необходимые *.h файлы, тогда компилятор компилит весь получившийся текст. Отсюда можно заключить что *.h файл компилится после обработки препроцессора.
Здравствуйте, Alxndr, Вы писали:
A>*.h файлы компилятором не обрабатываются.
Вот тут я не согласен . В *.h файле те же объявления которые могли бы быть и в *.сpp файле, и эти объявления компилируются в бинарный код.
Здравствуйте, physic, Вы писали:
P>Вот тут я не согласен . В *.h файле те же объявления которые могли бы быть и в *.сpp файле, и эти объявления компилируются в бинарный код.
Ачто делает этот год?
Здравствуйте, physic, Вы писали:
P>Здравствуйте, Alxndr, Вы писали:
A>>*.h файлы компилятором не обрабатываются.
P>Вот тут я не согласен . В *.h файле те же объявления которые могли бы быть и в *.сpp файле, и эти объявления компилируются в бинарный код.
После фазы препроцессинга иммем единицу трансляции следующего содержания:
Кури, вообщем, Стандарт и книги.
Здравствуйте, physic, Вы писали:
P>Здравствуйте, physic, Вы писали:
P>>как можно назвать обработку и подклучение *.h файла в C++ проекте?
P>Дело в том что *.h файл подключается к *.cpp или *.h файлу препроцессором, а вот когда у компилятора есть полностью скомпонованная единица трансляции *.cpp + все необходимые *.h файлы, тогда компилятор компилит весь получившийся текст. Отсюда можно заключить что *.h файл компилится после обработки препроцессора.
Путаница в терминологии
Компилятор компилирует единицы трансляции.
В системах, где исходные тексты хранятся в файлах, единица трансляции — это файл, обработанный препроцессорос с рекурсивно включенными *.h-файлами.
Здравствуйте, physic, Вы писали:
P>Вот тут я не согласен . В *.h файле те же объявления которые могли бы быть и в *.сpp файле, и эти объявления компилируются в бинарный код.
При чем здесь объявления? .
Компилятор строит свои внутренние таблицы: функция, параметр, возвращаемое значение; а более что ещё можно сделать?
Потом эти таблицы не трогаются если *.h не меняется. А если меняется, то таблица перестраивается, и компилятся все файлы куда он входил. Что-то вроде этого. Что за код может сгенириться из объявлений?
Crackjack wrote:
> A>Препроцессингом?
>
> В .h объявляются ф-ии, т.д. Возможна реализация, но не инициализация.
Там возможно всё что угодно. Правда в некоторых случаях (когда один h включается из нескольких cpp) программа не слинкуется.
> Таким образом, всё что будет необходимо твоему *.c будет взято из *.h,
> остальное игнорируется. Определение включению заголовочных файлов не
> скажу, не задавался таким вопросом, раньше не где ни встречал.
Обработка .h файлов идёт до компиляции. Компилятор вообще ничего про файлы не знает.
.cpp и .h файлы — чистая условность. Просто так договорились, языку до этого пофиг. Называй файлы как хочешь и что
угодно куда угодно подключай.
именно
Плюс можно вспомнить про precompiled header(msdn). Он применяется как раз для того, чтобы не компилировать многократно одни и те же куски кода, и представляет собой the state of compilation at a certain point.
Здравствуйте, physic, Вы писали:
P>как можно назвать обработку и подклучение *.h файла в C++ проекте?
Здравствуйте, Crackjack, Вы писали:
P>>>как можно назвать обработку и подклучение *.h файла в C++ проекте?
A>>Препроцессингом?
C>В .h объявляются ф-ии, т.д. Возможна реализация, но не инициализация. Таким образом, всё что будет необходимо твоему *.c будет взято из *.h, остальное игнорируется. Определение включению заголовочных файлов не скажу, не задавался таким вопросом, раньше не где ни встречал.
Здравствуйте, physic, Вы писали:
P>как можно назвать обработку и подклучение *.h файла в C++ проекте?
Прикольно реализовано в Boost.Test, там реализация лежит в *.ipp которая инклюдиться в файл, в первый раз такое вижу.
Часть 1. Вступление
Часть 2. Заголовочные файлы
Часть 3. Область видимости
…
Все мы при написании кода пользуемся правилами оформления кода. Иногда изобретаются свои правила, в других случаях используются готовые стайлгайды. Хотя все C++ программисты читают на английском легче, чем на родном, приятнее иметь руководство на последнем.
Эта статья является переводом части руководства Google по стилю в C++ на русский язык.
Исходная статья (fork на github), обновляемый перевод.
Заголовочные файлы
Желательно, чтобы каждый .cc файл исходного кода имел парный .h заголовочный файл. Также есть известные исключения из этого правила, такие как юниттесты или небольшие .cc файлы, содержащие только функцию main().
Правильное использование заголовочных файлов может оказать огромное влияние на читабельность, размер и производительность вашего кода.
Следующие правила позволят избежать частых проблем с заголовочными файлами.
Предварительное объявление
Определение
«Предварительное объявление» — декларация класса, функции, шаблона без соответствующего определения.
Встраиваемые (inline) функции
Определяйте функции как встраиваемые только когда они маленькие, например не более 10 строк.
Определение
Вы можете объявлять функции встраиваемыми и указать компилятору на возможность включать её напрямую в вызывающий код, помимо стандартного способа с вызовом функции.
Использование встраиваемых функций может генерировать более эффективный код, особенно когда функции маленькие. Используйте эту возможность для get/set функций, других коротких и критичных для производительности функций.
Чрезмерное использование встраиваемых функций может сделать программу медленнее. Также встраиваемые функции, в зависимости от размера её, могут как увеличить, так и уменьшить размер кода. Если это маленькие функции, то код может быть уменьшен. Если же функция большая, то размер кода может очень сильно вырасти. Учтите, что на современных процессорах более компактный код выполняется быстрее благодаря лучшему использованию кэша инструкций.
Хорошим правилом будет не делать функции встраиваемыми, если они превышают 10 строк кода. Избегайте делать встраиваемыми деструкторы, т.к. они неявно могут содержать много дополнительного кода: вызовы деструкторов переменных и базовых классов!
Ещё одно хорошее правило: обычно нет смысла делать встраиваемыми функции, в которых есть циклы или операции switch (кроме вырожденных случаев, когда цикл или другие операторы никогда не выполняются).
Важно понимать, что встраиваемая функция не обязательно будет скомпилирована в код именно так. Например, обычно виртуальные и рекурсивные функции компилируются со стандартным вызовом. Вообще, рекурсивные функции не должны объявляться встраиваемыми. Основная же причина делать встраиваемые виртуальные функции — разместить определение (код) в самом определении класса (для документирования поведения или удобства чтения) — часто используется для get/set функций.
Блокировка от повторного включения
Для гарантии уникальности, используйте компоненты полного пути к файлу в дереве проекта. Например, файл foo/src/bar/baz.h в проекте foo может иметь следующую блокировку:
Независимые заголовочные файлы
Заголовочные файлы должны быть самодостаточными (в плане компиляции) и иметь расширение .h. Другие файлы (не заголовочные), предназначенные для включения в код, должны быть с расширением .inc и использоваться в паре с включающим кодом.
Все заголовочные файлы должны быть самодостаточыми. Пользователи и инструменты разработки не должны зависеть от специальных зависимостей при использовании заголовочного файла. Заголовочный файл должен иметь блокировку от повторного включения и включать все необходимые файлы.
Предпочтительно размещать определения для шаблонов и inline-функций в одном файле с их декларациями. И эти определения должны быть включены (include) в каждый .cc файл, использующий их, иначе могут быть ошибки линковки на некоторых конфигурациях сборки. Если же декларации и определения находятся в разных файлах, включение одного должно подключать другой. Не выделяйте определения в отдельные заголовочные файлы (-inl.h). Раньше такая практика была очень популярна, сейчас это нежелательно.
Как исключение, если из шаблона создаются все доступные варианты шаблонных аргументов или если шаблон реализует функционал, используемый только одним классом — тогда допустимо определять шаблон в одном (и только одном) .cc файле, в котором этот шаблон и используется.
Возможны редкие ситуации, когда заголовочный файл не самодостаточный. Это может происходить, когда файл подключается в нестандартном месте, например в середине другого файла. В этом случае может отсутствовать блокировка от повторного включения, и дополнительные заголовочные файлы также могут не подключаться. Именуйте такие файлы расширением .inc. Используйте их парой и старайтесь чтобы они максимально соответствовали общим требованиям.
Имена и Порядок включения (include)
Вставляйте заголовочные файлы в следующем порядке: парный файл (например, foo.h — foo.cc), системные файлы C, стандартная библиотека C++, другие библиотеки, файлы вашего проекта.
Все заголовочные файлы проекта должны указываться относительно директории исходных файлов проекта без использования таких UNIX псевдонимов как . (текущая директория) или .. (родительская директория). Например, google-awesome-project/src/base/logging.h должен включаться так:
Другой пример: если основная функция файлов dir/foo.cc иdir/foo_test.cc это реализация и тестирование кода, объявленного в dir2/foo2.h, то записывайте заголовочные файлы в следующем порядке:
- dir2/foo2.h.
- — Пустая строка
- Системные заголовочные файлы C (точнее: файлы с включением угловыми скобками с расширением .h), например , .
- — Пустая строка
- Заголовочные файлы стандартной библиотеки C++ (без расширения в файлах), например , .
- — Пустая строка
- Заголовочные .h файлы других библиотек.
- Файлы .h вашего проекта.
Такой порядок файлов позволяет выявить ошибки, когда в парном заголовочном файле (dir2/foo2.h) пропущены необходимые заголовочные файлы (системные и др.) и сборка соответствующих файлов dir/foo.cc или dir/foo_test.cc завершится ошибкой. Как результат, ошибка сразу же появится у разработчика, работающего с этими файлами (а не у другой команды, которая только использует внешнюю библиотеку).
Обычно парные файлы dir/foo.cc и dir2/foo2.h находятся в одной директории (например, base/basictypes_test.cc и base/basictypes.h), хотя это не обязательно.
Учтите, что заголовочные файлы C, такие как stddef.h обычно взаимозаменяемы соответствующими файлами C++ (cstddef). Можно использовать любой вариант, но лучше следовать стилю существующего кода.
Внутри каждой секции заголовочные файлы лучше всего перечислять в алфавитном порядке. Учтите, что ранее написанный код может не следовать этому правилу. По возможности (например, при исправлениях в файле), исправляйте порядок файлов на правильный.
Следует включать все заголовочные файлы, которые объявляют требуемые вам типы, за исключением случаев предварительного объявления. Если ваш код использует типы из bar.h, не полагайтесь на то, что другой файл foo.h включает bar.h и вы можете ограничиться включением только foo.h: включайте явно bar.h (кроме случаев, когда явно указано (возможно, в документации), что foo.h также выдаст вам типы из bar.h).
Например, список заголовочных файлов в google-awesome-project/src/foo/internal/fooserver.cc может выглядеть так:
Бывают случаи, когда требуется включение заголовочных файлов в зависимости от условий препроцессора (например, в зависимости от используемой ОС). Такое включение старайтесь делать как можно короче (локализованно) и размещать после других заголовочных файлов. Например:
Читайте также: