Що повинно бути, а що не повинно бути у файлі заголовка? [зачинено]


71

Які речі абсолютно ніколи не повинні включатись у файл заголовка?

Якщо, наприклад, я працюю з документально зафіксованим галузевим стандартним форматом, який має багато констант, чи є хорошою практикою визначити їх у файлі заголовка (якщо я пишу парсер для цього формату)?

Які функції повинні входити у файл заголовка?
Які функції не повинні?


1
Короткі та безболісні: визначення та декларації, які потрібні у більш ніж одному модулі.
ott--

21
Позначення цього питання "занадто широким" і закриття є абсолютною ганебною надмірністю поміркованості. Це питання задає саме те, що я шукаю - запитання добре сформовано і задає дуже чітке запитання: які найкращі практики? Якщо це "занадто широко" для програмної інженерії .. ми могли б також просто закрити весь цей форум.
Gewure

TL; ЛІКАР. Що стосується C ++, у четвертому виданні "Мова програмування на C ++", написаному Б'ярном Струструпом (його творцем), у Розділі 15.2.2 описано, що має містити заголовок, а що не повинен. Я знаю, що ви позначили це питання на С, але деякі поради також застосовні. Я думаю, що це гарне питання ...
horro

Відповіді:


57

Що поставити в заголовки:

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

Що не належить до заголовка:

  • Безкоштовні #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, чи повинен Я просто вперед заявляю про це? Бібліотека А збирається та пов'язана з бібліотекою Б під час її складання.
MarcusJ

@MarcusJ - Найперше, що я вказав під заголовком Що не належить до заголовка, були бездоганні заяви #include. Якщо файл заголовка B не залежить від визначень у файлі заголовка A, не включайте файл заголовка A у файл заголовка B. Файл заголовка не є місцем для визначення сторонніх залежностей чи інструкцій щодо побудови. Вони йдуть де-небудь ще, наприклад, файл readme верхнього рівня.
Девід Хаммен

1
@MarcusJ - я оновив свою відповідь, намагаючись відповісти на ваше запитання. Зауважте, що на ваше запитання немає жодної відповіді. Я проілюструю з парою крайнощів. Випадок 1: Єдине місце, де бібліотека B безпосередньо використовує функціональність бібліотеки A, - це вихідні файли бібліотеки B. Випадок 2: Бібліотека B - це невелике розширення функціональності бібліотеки A, у файлі заголовка для бібліотеки B безпосередньо з використанням типів та / або функцій, визначених у бібліотеці А. У випадку 1 немає жодної причини виставляти бібліотеку A у заголовок бібліотеки B. У випадку 2 ця експозиція є майже обов'язковою.
Девід Хаммен

Так, це випадок 2, вибачте, що мій коментар пропустив через те, що він використовує типи, оголошені в бібліотеці А, в заголовках бібліотеки B, я думав, що, можливо, я можу переслати його, але не думаю, що це не спрацює. Дякуємо за оновлення.
MarcusJ

Чи велике додавання констант до файлу заголовка - ні-ні?
mding5692

15

Крім того, що вже було сказано.

H-файли завжди повинні містити:

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

Файли H ніколи не повинні містити:

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

(Я б також сказав, що ніколи не виникає причин використовувати незмінні глобальні / зовнішні змінні в будь-якому місці, але це обговорення для іншої публікації.)


1
Я погоджуюся з усім, крім того, що ви виділили. Якщо ви створюєте бібліотеку, так, ви повинні документувати або користувачів вашої бібліотеки. Для внутрішнього проекту вам не потрібно буде захаращувати заголовки документацією, якщо ви використовуєте хороші, пояснювальні імена змінних та функцій, що роз'яснюються самостійно.
martiert

5
@martiert Я також з школи "нехай код говорить сам". Однак ви повинні, принаймні, завжди документувати свої функції, навіть якщо ніхто, крім вас самих, ними не користуватиметься. Цікаві особливі речі: якщо функція має обробку помилок, які коди помилок вона повертає та за яких умов вона не працює? Що відбувається з параметрами (буфери, покажчики тощо), якщо функція не працює? Ще одна важлива річ: чи параметри вказівника щось повертають абоненту, тобто чи очікують вони виділеної пам'яті? ->

1
Для абонента повинно бути очевидним, що обробляється помилками всередині функції, а що не робиться. Якщо функція очікує виділеного буфера, вона, швидше за все, залишить чекові, що виходять за рамки, також. Якщо функція покладається на іншу функцію, яку потрібно виконати, це потрібно задокументувати (тобто запустити link_list_init () перед link_list_add ()). І нарешті, якщо функція має «побічний ефект», такий як створення файлів, потоків, таймерів чи будь-чого іншого, це слід зазначити в документації. ->

1
Можливо, "документація на вихідний код" тут занадто широка, це дійсно належить до вихідного коду. "Документація щодо використання" із введенням та виведенням, попередніми та пост-умовами та побічними ефектами, безумовно, повинна міститись там не в епічному, а в стислому вигляді.
Безпечний

2
Трохи запізнілий, але +1 для документації. Чому цей клас існує? Код не говорить сам за себе. Що ця функція виконує? RTFC (читати чудовий .cpp файл) - це нецензурна абревіатура з чотирьох літер. Ніколи не слід мати RTFC для розуміння. Прототип у заголовку повинен у підсумковому коментарі (наприклад, доксиген) узагальнити, що таке аргументи та що функція виконує. Чому цей член даних існує, що він містить і чи це значення в метрах, футах чи фулонгонах? Це теж є ще однією темою для (витягуючих) коментарів.
Девід Хаммен

4

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

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

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

Деякі можуть не погодитися, але мої особисті критерії для файлів .h полягають у тому, що вони # включають усі інші .h файли, які їм потрібно мати можливість складати. У деяких випадках це може бути багато файлів, тому у нас є кілька ефективних методів зменшення зовнішніх залежностей, таких як переадресація оголошень до класів, які дозволяють нам використовувати покажчики на об’єкти класу, не включаючи те, що може бути великим деревом файлів включення.


3

Файл заголовка повинен мати таку організацію:

  • тип та постійні визначення
  • декларації зовнішніх об'єктів
  • зовнішні декларації функції

Файли заголовків ніколи не повинні містити визначення об’єктів, лише визначення типів та декларації об'єкта.


Що щодо визначення вбудованих функцій?
Кос

Якщо вбудована функція - це "помічниця", яка використовується тільки в одному модулі С, тоді введіть її лише у цей файл .c Якщо функція вбудовування повинна бути видимою для двох або більше модулів, помістіть її всередині заголовкового файлу.
theD

Крім того, якщо функція повинна бути видимою через межу бібліотеки, не вказуйте її на вбудовану, оскільки це змушує всіх, хто використовує бібліотеку, перекомпілювати кожен раз, коли ви змінюєте речі.
Дональні стипендіати

@DonalFellows: це зворотне рішення. Краще правило: не кладіть речі в заголовки, які часто піддаються змінам. Немає нічого поганого в тому, щоб у заголовку було вбудовано коротку маленьку функцію, якщо функція не має вентилятора і має чітке визначення, яке зміниться лише у випадку зміни базової структури даних. Якщо визначення функції змінюється через те, що основне визначення структури змінилося, так, вам доведеться перекомпілювати все, але все одно це доведеться робити, оскільки змінилося визначення структури.
Девід Хаммен

0

Заяви, які генерують дані та код під час їх розбору, не повинні містити .hфайл. Що стосується моєї точки зору, файл заголовка повинен мати лише мінімальний практичний інтерфейс до відповідного .cабо .cpp.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.