Что должно и что не должно быть в заголовочном файле? [закрыто]

71

Какие вещи абсолютно никогда не должны быть включены в заголовочный файл?

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

Какие функции должны идти в заголовочный файл?
Какие функции не должны?

Моше Магнес
источник
1
Коротко и безболезненно: определения и определения, которые необходимы более чем в одном модуле.
ott--
21
Отмечать этот вопрос как «слишком широкий» и закрытие - это абсолютное позорное излишество умеренности. Этот вопрос точно задает то, что я ищу - Вопрос хорошо сформулирован и задает очень четкий вопрос: каковы лучшие практики? Если это "слишком широко" для разработки программного обеспечения ... мы могли бы просто закрыть весь этот форум.
Gewure
TL; DR. Для C ++ в четвертом издании «Языка программирования C ++», написанном Бьярном Страуструпом (его создателем), в Разделе 15.2.2 описано, что должен и не должен содержать заголовок. Я знаю, что вы пометили вопрос на C, но некоторые советы также применимы. Я думаю, что это хороший вопрос ...
Horro

Ответы:

57

Что положить в заголовки:

  • Минимальный набор #includeдиректив, необходимых для того, чтобы сделать заголовок компилируемым, когда заголовок включен в некоторый исходный файл.
  • Определения символов препроцессора вещей, которые должны быть разделены и которые могут быть выполнены только через препроцессор. Даже в C символы препроцессора лучше всего сводятся к минимуму.
  • Прямые объявления структур, которые необходимы для компиляции определений структуры, прототипов функций и объявлений глобальных переменных в теле заголовка.
  • Определения структур данных и перечислений, которые совместно используются несколькими исходными файлами.
  • Объявления для функций и переменных, определения которых будут видны компоновщику.
  • Встроенные определения функций, но будьте осторожны здесь.

Что не принадлежит в заголовке:

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

Что составляет минимальный набор #includeутверждений?

Это оказывается нетривиальным вопросом. Определение TL; DR: файл заголовка должен включать файлы заголовка, которые непосредственно определяют каждый из типов, непосредственно используемых в них, или которые непосредственно объявляют каждую из функций, используемых в рассматриваемом файле заголовка, но не должны включать ничего другого. Указатель или ссылочный тип C ++ не квалифицируются как прямое использование; прямые ссылки являются предпочтительными.

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

#include "path/to/random/header_under_test"
int main () { return 0; }

Компиляция должна быть чистой (то есть без каких-либо предупреждений или ошибок). Предупреждения или ошибки, касающиеся неполных типов или неизвестных типов, означают, что в тестируемом заголовочном файле есть некоторые отсутствующие #includeдирективы и / или отсутствующие предварительные объявления. Обратите внимание: только то, что тесты пройдены, не означает, что набор #includeдиректив достаточен, не говоря уже о минимуме.

Дэвид Хаммен
источник
Итак, если у меня есть библиотека, которая определяет структуру, называемую A, и эта библиотека, называемая B, использует эту структуру, а библиотека B используется программой C, должен ли я включить заголовочный файл библиотеки A в основной заголовок библиотеки B или должен Я просто жду объявить это? библиотека A компилируется и связывается с библиотекой B во время ее компиляции.
MarcusJ
@MarcusJ. Самым первым, что я перечислил в разделе « Что не входит в заголовок», были необоснованные заявления #include. Если заголовочный файл B не зависит от определений в заголовочном файле A, не включайте #include заголовочный файл A в заголовочный файл B. Заголовочный файл не предназначен для указания сторонних зависимостей или инструкций по сборке. Они идут куда-то еще, например, в файл readme верхнего уровня.
Дэвид Хаммен
1
@MarcusJ - я обновил свой ответ, пытаясь ответить на ваш вопрос. Обратите внимание, что нет одного ответа на ваш вопрос. Я проиллюстрирую пару крайностей. Случай 1: Единственное место, где библиотека B напрямую использует функциональные возможности библиотеки A, - это исходные файлы библиотеки B. Случай 2: Библиотека B является тонким расширением функциональности в библиотеке A, с заголовочными файлами для библиотеки B, непосредственно использующими типы и / или функции, определенные в библиотеке A. В случае 1 нет причин выставлять библиотеку A в заголовок (и) для библиотеки B. В случае 2 это воздействие в значительной степени является обязательным.
Дэвид Хаммен
Да, это случай 2, извините, мой комментарий пропустил тот факт, что он использует типы, объявленные в библиотеке A в заголовках библиотеки B, я думал, что смогу объявить это вперед, но я не думаю, что это сработает. Спасибо за обновления.
MarcusJ
Является ли добавление констант в заголовочный файл большим нет-нет?
mding5692
15

В дополнение к тому, что уже было сказано.

H файлы должны всегда содержать:

  • Документация по исходному коду !!! Как минимум, какова цель различных параметров и возвращаемых значений функций.
  • Охранники заголовков, #ifndef MYHEADER_H #define MYHEADER_H ... #endif

H-файлы никогда не должны содержать:

  • Любая форма размещения данных.
  • Определения функций. Встроенные функции могут быть редким исключением в некоторых случаях.
  • Все, что помечено static.
  • Typedefs, #defines или константы, которые не имеют отношения к остальной части приложения.

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


источник
1
Я согласен со всем, кроме того, что вы подчеркнули. Если вы делаете библиотеку, да, вы должны документировать или пользователей вашей библиотеки. Для внутреннего проекта вам не нужно загромождать заголовки документацией, если вы используете хорошие, не требующие пояснений имена переменных и функций.
Мартиерт
5
@martiert Я также из школы "пусть код говорит сам за себя". Тем не менее, вы должны как минимум всегда документировать свои функции, даже если никто, кроме вас, не будет их использовать. Особый интерес представляют следующие вопросы: если функция обрабатывает ошибки, какие коды ошибок она возвращает и при каких условиях она не срабатывает? Что происходит с параметрами (буферами, указателями и т. Д.) В случае сбоя функции? Еще одна вещь, которая очень важна: возвращают ли параметры указателя вызывающей стороне, то есть ожидают ли они выделенной памяти? ->
1
Для вызывающей стороны должно быть очевидно, какая обработка ошибок выполняется внутри функции, а какая - нет. Если функция ожидает выделенный буфер, она, скорее всего, также оставит проверки за пределами границ вызывающей стороне. Если функция полагается на другую функцию, которая должна быть выполнена, это должно быть задокументировано (то есть запустить link_list_init () перед link_list_add ()). И, наконец, если у функции есть «побочный эффект», такой как создание файлов, потоков, таймеров или чего-либо еще, это должно быть указано в документации. ->
1
Может быть, «документация по исходному коду» здесь слишком широка, это действительно относится к исходному коду. «Документация по использованию» с вводом и выводом, предварительными и постусловиями и побочными эффектами обязательно должна быть там, не в эпической, а в краткой форме.
Безопасное
2
Немного запоздалый, но +1 за документацию. Почему этот класс существует? Код не говорит сам за себя. Что делает эта функция? RTFC (читайте прекрасный файл .cpp) - это непристойный четырехбуквенный аббревиатура. Никогда не нужно RTFC для понимания. Прототип в заголовке должен резюмировать в некоторых извлекаемых комментариях (например, doxygen), каковы аргументы и что делает функция. Почему существует этот элемент данных, что он содержит, и какое значение имеет метры, футы или фарлонги? Это тоже еще одна тема для (извлекаемых) комментариев.
Дэвид Хаммен,
4

Я, вероятно, никогда не скажу никогда, но операторы, которые генерируют данные и код при их разборе, не должны быть в файле .h.

Макросы, встроенные функции и шаблоны могут выглядеть как данные или код, но они генерируют код не при разборе, а при использовании. Эти элементы часто нужно использовать в нескольких .c или .cpp, поэтому они принадлежат .h.

На мой взгляд, заголовочный файл должен иметь минимальный практический интерфейс к соответствующему .c или .cpp. Интерфейс может включать в себя #defines, class, typedef, определения структур, прототипы функций и менее предпочтительные внешние определения для глобальных переменных. Однако, если объявление используется только в одном исходном файле, его, вероятно, следует исключить из .h и вместо этого содержать в исходном файле.

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

DeveloperDon
источник
3

Заголовочный файл должен иметь следующую организацию:

  • определения типов и констант
  • объявления внешних объектов
  • объявления внешних функций

Заголовочные файлы никогда не должны содержать определения объектов, только определения типов и объявления объектов.

Тед
источник
Как насчет определения встроенных функций?
Кос
Если встроенная функция является «вспомогательной» функцией, которая используется только внутри одного модуля C, поместите ее только в этот файл .c. Если встроенная функция должна быть видимой для двух или более модулей, поместите ее в заголовочный файл.
Тед
Кроме того, если функция должна быть видимой за границей библиотеки, не делайте ее встроенной, так как это заставляет каждого, кто использует библиотеку, перекомпилировать каждый раз, когда вы что-то изменяете.
Донал Феллоуз
@DonalFellows: это скрытое решение. Лучшее правило: не помещайте вещи в заголовки, которые часто изменяются. Нет ничего плохого в том, чтобы встроить короткую маленькую функцию в заголовок, если функция не имеет разветвления и имеет четкое определение, которое будет меняться только при изменении базовой структуры данных. Если определение функции изменяется из-за изменения определения базовой структуры, да, вам придется все перекомпилировать, но вам все равно придется это делать, потому что определение структуры изменилось.
Дэвид Хаммен
0

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

Аджай Прасад
источник