C / C ++ включають порядок файлів заголовка


288

Який порядок повинен містити вказані файли, тобто які причини включення одного заголовка до іншого?

Наприклад, чи йдуть системні файли, STL та Boost до або після того, як локальні включають файли?


2
І безліч відповідей нижче, чому розробники Java вирішили проти окремих заголовків. :-) Декілька справді хороших відповідей, однак, особливо застереження, щоб переконатися, що ваші власні файли заголовків можуть стояти окремо.
Кріс К

37
Мені подобається, що питання, які мають 100 голосів і є цікавими для багатьох людей, закриваються як "неконструктивні".
Андреас

Настійно рекомендуємо прочитати: cplusplus.com/forum/articles/10627
Kalsan

3
@mrt, SO наполегливо нагадує нацистську спільноту супу: або ви дотримуєтесь дуже суворих правил, або "Немає належної відповіді / коментарів для вас!". Тим не менше, якщо у когось є проблеми, пов’язані з будь-яким способом програмування, це (зазвичай) перший сайт, який зайшов ..
Imago

Відповіді:


289

Я не думаю, що є рекомендоване замовлення, поки воно складається. Що дратує, коли одні заголовки вимагають, щоб перші заголовки були включені першими ... Це проблема з самими заголовками, а не з порядком включень.

Мої особисті переваги - перейти від локального до глобального, кожен підрозділ в алфавітному порядку, тобто:

  1. h файл, відповідний цьому файлу cpp (якщо застосовується)
  2. заголовки з того ж компонента,
  3. заголовки з інших компонентів,
  4. системні заголовки.

Моє обґрунтування 1. полягає в тому, що він повинен довести, що кожен заголовок (для якого є cpp) може бути #included без передумов (terminus technicus: заголовок "автономний"). А решта просто схоже логічно витікає звідти.


16
Приблизно такий же, як і ви, за винятком того, що я переходжу від глобального до локального, і заголовок, що відповідає вихідному файлу, не отримує особливої ​​обробки.
Джон Перді

127
@Jon: Я б сказав, що навпаки! :-) Я заперечую, що ваш метод може вводити приховані залежності, скажімо, якщо myclass.cpp включає <string>, то <myclass.h>, немає способу зафіксувати час збирання, що myclass.h сам може залежати від рядка; тож якщо пізніше ви або хтось інший включає myclass.h, але не потрібна рядок, ви отримаєте помилку, яку потрібно виправити або в cpp, або в самому заголовку. Але мені було б цікаво дізнатись, якщо люди вважають, що в довгостроковій перспективі краще працюватиме ... Чому б ви не опублікуєте відповідь зі своєю пропозицією, і ми побачимо, хто "перемагає"? ;-)
скрилат

3
Особливістю загального замовлення є те, що я використовую в даний час з рекомендацій Дейва Абрахамса. І він зазначає ту ж причину, що і @squelart висвітлення пропущеного заголовка міститься у джерелах, від локальних до більш загальних. Важливим ключовим є те, що ви, швидше за все, помиляєтесь, ніж сторонні та системні бібліотеки.
GrafikRobot

7
@PaulJansen Це погана практика, і добре використовувати техніку, яка, швидше за все, підірватиме її, щоб погану практику можна було виправити, а не приховувати. локальний до глобального FTW
bames53

10
@PaulJansen Так, я мав на увазі перевищення стандартної поведінки. Це може статися випадково так само, як, наприклад, порушення ОДР може статися випадково. Рішення полягає не в тому, щоб використовувати практики, які приховуються, коли трапляються такі аварії, а використовувати практики, які, швидше за все, змушують їх підірватись якомога голосніше, щоб помилки можна було помітити і виправити якомога раніше.
bames53

106

Варто пам’ятати, що ваші заголовки не повинні залежати від того, щоб перші заголовки були включені першими. Один із способів убезпечити це - включити заголовки до інших заголовків.

Зокрема, про це згадує "Думаючи про C ++", посилаючись на "Великий масштаб C ++ Software Design" Лакоса:

Приховані помилки використання можна уникнути, переконавшись, що .h файл компонента сам розбирає - без наданих зовні декларацій чи визначень ... У тому числі .h файл як перший рядок файлу .c забезпечує відсутність критичного фрагмента Інформація, властива фізичному інтерфейсу компонента, відсутня у файлі .h (або, якщо є, ви дізнаєтесь про нього, як тільки спробуєте зібрати файл .c).

Тобто, включіть у такому порядку:

  1. Прототип / заголовок інтерфейсу для цієї реалізації (тобто файл .h / .hh, що відповідає цьому файлу .cpp / .cc).
  2. Інші заголовки цього ж проекту, за потребою.
  3. Заголовки інших нестандартних, несистемних бібліотек (наприклад, Qt, Eigen тощо).
  4. Заголовки інших "майже стандартних" бібліотек (наприклад, Boost)
  5. Стандартні заголовки C ++ (наприклад, iostream, funkcionalні тощо)
  6. Стандартні заголовки C (наприклад, cstdint, dirent.h тощо)

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

Посібник зі стилів Google C ++ стверджує майже зворотне, насправді зовсім не виправдання; Я особисто схильний до підходу Лакоса.


13
Відтепер Посібник зі стилів Google C ++ рекомендує спочатку включити відповідний файл заголовка, після пропозицій Лакоса.
Філіп Бартек

Ви не можете набагато перевищити перший пов’язаний заголовок, тому що, коли ви починаєте включати заголовки в проект, ви збираєтеся мати багато системних залежностей.
Міхей

@Micah - заголовки в проекті, що втягують "багато системних залежностей" - це поганий дизайн, саме того, чого ми намагаємося тут уникати. Сенс у тому, щоб уникнути як непотрібних, так і невирішених залежностей. Усі заголовки повинні бути включені без попереднього включення інших заголовків. У тому випадку, коли для заголовка в проекті потрібна системна залежність, так і бути - тоді ви не включаєте (і не повинні) включати системну залежність після неї, якщо тільки місцевий код цього файлу не використовує речі з цієї системи dep. Ви не можете і не повинні покладатися на заголовки (навіть ваші) для включення системних деп, які ви використовуєте.
Натан Пол Сімонс

49

Я дотримуюся двох простих правил, які уникають переважної більшості проблем:

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

Я також дотримуюся вказівок:

  1. Спочатку включіть заголовки системи (stdio.h тощо) з роздільною лінією.
  2. Згрупуйте їх логічно.

Іншими словами:

#include <stdio.h>
#include <string.h>

#include "btree.h"
#include "collect_hash.h"
#include "collect_arraylist.h"
#include "globals.h"

Хоча, будучи настановами, це суб’єктивна річ. Правила з іншого боку, я суворо виконую, навіть до того, щоб забезпечити "загорткові" файли заголовків з включенням охоронців і згрупованих включає, якщо якийсь недобрий сторонній розробник не підписався на моє бачення :-)


6
+1 "Усі заголовки (і справді будь-які вихідні файли) повинні містити те, що їм потрібно. Вони не повинні покладатися на своїх користувачів, включаючи речі". Тим не менше, так багато людей покладаються на цю неявну поведінку включення, наприклад, з NULL і не включають <cstddef>. Це так дратує, коли намагаються перенести цей код і отримувати помилки компіляції на NULL (одна з причин, яку я зараз використовую 0).
stinky472

20
Чому ви спочатку включаєте заголовки системи? Було б краще інше, чому навколо, через ваше перше правило.
jhasse

Якщо ви використовуєте тестові макроси функції, ваш перший включення, швидше за все, НЕ повинен бути стандартним заголовком бібліотеки. Тому, для загального розуміння, я б сказав, що найкраща "локальна спочатку, глобальна пізніше" політика.
hmijail оплакує відставки

1
Що стосується вашої першої пропозиції "не покладатися на користувачів", що робити з переадресацією декларацій у файлі заголовка, які не потребують включення файлу заголовка? Чи слід все-таки включати файл заголовка, тому що прямі декларації ставлять на користувача користувача заголовкового файлу включення відповідних файлів.
Зосо

22

Щоб додати до стіни власну цеглу.

  1. Кожен заголовок повинен бути самодостатнім, що можна перевірити, лише якщо він включений спочатку хоча б один раз
  2. Не слід помилково змінювати значення стороннього заголовка, вводячи символи (макрос, типи тощо)

Тому я зазвичай так:

// myproject/src/example.cpp
#include "myproject/example.h"

#include <algorithm>
#include <set>
#include <vector>

#include <3rdparty/foo.h>
#include <3rdparty/bar.h>

#include "myproject/another.h"
#include "myproject/specific/bla.h"

#include "detail/impl.h"

Кожна група відокремлена порожнім рядком від наступної:

  • Перший заголовок, відповідний цьому файлу cpp (перевірка обґрунтованості)
  • Системні заголовки
  • Сторонні заголовки, організовані за наказом залежності
  • Заголовки проектів
  • Проекти приватних заголовків

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


2
Так що інші файли заголовків на них не впливають. І те, що визначають заголовки системи (включаючи X, і Windows включає, погано #defineсприймає інший код) та запобігає неявним залежностям. Наприклад, якщо foo.hнасправді залежить наш заголовок коду базового коду, <map>але скрізь, де він був використаний у .ccфайлах, <map>трапилось, що він уже включений, ми, ймовірно, не помітимо. Поки хтось не спробував включити, попередньо foo.hне включаючи <map>. І тоді вони будуть роздратовані.

@ 0A0D: Другий випуск не є проблемою в наказі тут, оскільки кожен .hмає принаймні один, .cppякий включає його першим (дійсно, в моєму особистому коді тест Unit, пов’язаний з ним, включає його першим, а вихідний код включає його у свою правильну групу ). Що стосується впливу, якщо будь-який із заголовків включає <map>будь-які заголовки, включені після цього, все одно впливають, тому для мене це здається програшним боєм.
Матьє М.

1
Звичайно, саме тому я регулярно обходжу і виправляю старіший код (або навіть новіший код), який вимагає непотрібного включення, оскільки він просто сприяє нарощуванню часу нарощування.

@MatthieuM. Я хотів би знати обґрунтування Вашого пункту, тобто Header corresponding to this cpp file first (sanity check). Чи є щось особливе, якщо #include "myproject/example.h"переміщено до кінця всіх включених?
MNS

1
@MNS: Заголовок повинен бути вільним, тобто перед ним не потрібно містити жодного іншого заголовка. Ви зобов'язані переконатись у цьому як автор заголовка, і найкращий спосіб зробити це - мати один вихідний файл, у який цей заголовок включений першим. Використовувати вихідний файл, відповідний файлу заголовка, дуже просто, ще одним хорошим вибором є використання файлу вихідного тестового файлу, відповідного файлу заголовка, але він менш універсальний (можуть бути не тестові одиниці).
Матьє М.

16

Я рекомендую:

  1. Заголовок модуля .cc, який ви будуєте. (Допомагає гарантувати, що кожен заголовок у вашому проекті не має неявних залежностей від інших заголовків у вашому проекті.)
  2. C системні файли.
  3. Системні файли C ++.
  4. Платформа / ОС / інші файли заголовків (наприклад, win32, gtk, openGL).
  5. Інші файли заголовків вашого проекту.

І звичайно, в алфавітному порядку в кожному розділі, де це можливо.

Завжди використовуйте прямі декларації, щоб уникнути зайвих #includes у файлах заголовка.


+1, але чому в алфавітному порядку? Здається, це щось, що може змусити себе почувати себе краще, але не має жодної практичної користі.
Бен

9
Алфавіт - це довільне впорядкування, але просте. Вам не потрібно робити алфавіт, але вибирати замовлення потрібно так, щоб усі робили це послідовно. Я виявив, що це допомагає уникнути дублікатів та полегшує злиття. І якщо ви використовуєте піднесений текст, F5 замовить їх вам.
i_am_jorf

14

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

#include <set>
#include <vector>
#include <algorithm>
#include <functional>

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


3
Мені подобається сортувати заголовки за допомогою ключа, що складається з другої, третьої та першої літери в такому порядку :-) Отже, вектор, набір, алгоритм, функціональний для вашого прикладу.
paxdiablo

@paxdiablo, дякую за пораду. Я розглядаю можливість його використання, але я стурбований тим, що це може в кінцевому підсумку залишити купу файлів нестабільних і, швидше за все, перекинутися. Хто знає, що може бути включено, якщо це станеться - можливо, навіть windows.h.
clstrfsck

40
Відсортовано за довжиною ? Божевілля!
Джеймс Макнелл

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

6

Це не суб'єктивно. Переконайтеся, що ваші заголовки не покладаються на те, щоб бути #included у визначеному порядку. Ви можете бути впевнені, що це не має значення, до якого порядку ви додаєте заголовки STL або Boost.


1
Я припускав, що немає неявних залежностей
Anycorn

Так, але компілятор не може зробити це припущення, тому #include <A>, <B> ніколи не є тим самим, як #include <B>, <A>, поки вони не будуть складені.
Михайло

4

По- перше , включають заголовок , відповідний .cpp ... Іншими словами, source1.cppповинна включати в себе , source1.hперш ніж включати що - небудь ще. Єдиний виняток, про який я можу подумати - це використання MSVC з попередньо складеними заголовками, і в цьому випадку ви змушені включати stdafx.hраніше всього іншого.

Обґрунтування: Включення source1.hдо будь-яких інших файлів гарантує, що він може стояти самостійно без залежностей. Якщо source1.hпізніше набуде залежність, компілятор негайно попередить вас додати необхідні форвардні декларації до source1.h. Це в свою чергу гарантує, що заголовки можуть бути включені в будь-який порядок залежними від них.

Приклад:

джерело1.h

class Class1 {
    Class2 c2;    // a dependency which has not been forward declared
};

source1.cpp

#include "source1.h"    // now compiler will alert you saying that Class2 is undefined
                    // so you can forward declare Class2 within source1.h
...

Користувачі MSVC: Настійно рекомендую використовувати попередньо складені заголовки. Отже, перемістіть усі #includeдирективи для стандартних заголовків (та інших заголовків, які ніколи не зміняться) на stdafx.h.


2

Включіть від найбільш конкретного до найменш конкретного, починаючи з відповідного .hpp для .cpp, якщо такий існує. Таким чином, будуть виявлені будь-які приховані залежності у файлах заголовків, які не є самодостатніми.

Це ускладнюється використанням попередньо складених заголовків. Одним із способів цього є, не роблячи конкретного компілятора проекту, використовувати один із заголовків проекту в якості файлу заздалегідь складеного заголовка.


1

Це важке запитання у світі C / C ++, у якому стільки елементів перевищує стандарт.

Я думаю, що порядок заголовка файлів не є серйозною проблемою, доки він збирається, як це сказав.

Мої ідеї такі: якщо в усіх цих заголовках немає конфлікту символів, будь-який порядок є нормальним, і питання залежності заголовка можна виправити пізніше, додавши #include рядки до несправного .h.

Справжній клопот виникає, коли якийсь заголовок змінює свою дію (перевіряючи #if умови) відповідно до того, які заголовки вище.

Наприклад, у stddef.h у VS2005 є:

#ifdef  _WIN64
#define offsetof(s,m)   (size_t)( (ptrdiff_t)&(((s *)0)->m) )
#else
#define offsetof(s,m)   (size_t)&(((s *)0)->m)
#endif

Тепер проблема: Якщо у мене є власний заголовок ("custom.h"), який потрібно використовувати з багатьма компіляторами, включаючи деякі старіші, які не містять offsetofу своїх заголовках системи, я повинен написати у своєму заголовку:

#ifndef offsetof
#define offsetof(s,m)   (size_t)&(((s *)0)->m)
#endif

І не забудьте повідомити користувачеві #include "custom.h" після всіх заголовків системи, інакше рядок offsetofв stddef.h стверджує про помилку перегляду макросу.

Ми молимось не зустрічати більше таких випадків у своїй кар’єрі.

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