C / C ++ включает порядок заголовочных файлов


288

В каком порядке должны быть указаны включаемые файлы, т.е. каковы причины включения одного заголовка перед другим?

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


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

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

Настоятельно рекомендуется прочитать: cplusplus.com/forum/articles/10627
Калсан

3
@mrt, ТАК сильно напоминает суповое нацистское сообщество: либо вы следуете очень строгим правилам, либо «Нет правильного ответа / комментариев для вас!». Тем не менее, если у кого-то есть проблемы, связанные каким-либо образом с программированием, это (обычно) первый сайт, который нужно посетить ...
Имаго

Ответы:


289

Я не думаю, что есть рекомендуемый порядок, пока он компилируется! Что раздражает, так это то, что некоторые заголовки требуют, чтобы другие заголовки включались первыми ... Это проблема самих заголовков, а не порядка включений.

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

  1. h-файл, соответствующий этому cpp-файлу (если применимо)
  2. заголовки из того же компонента,
  3. заголовки из других компонентов,
  4. системные заголовки.

Мое обоснование для 1. состоит в том, что он должен доказать, что каждый заголовок (для которого есть cpp) может быть #included без предварительных условий (terminus technicus: header является «автономным»). А все остальное кажется логически вытекающим оттуда.


16
Почти так же, как и вы, за исключением того, что я перехожу с глобального на локальный, а заголовок, соответствующий исходному файлу, не получает специальной обработки.
Джон Перди

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

3
Специфика общего порядка - это то, что я использую в настоящее время по рекомендации Дейва Абрахамса. И он отмечает ту же причину, что и @squelart подсветки отсутствующего заголовка в источниках, от локальных до более общих. Важным ключом является то, что вы, скорее всего, сделаете эти ошибки, чем сторонние и системные библиотеки.
ГрафикРобот

7
@PaulJansen Это плохая практика, и хорошо использовать технику, которая с большей вероятностью взорвется, так что плохая практика может быть исправлена ​​вместо того, чтобы прятаться. местный на глобальный FTW
bames53

10
@PaulJansen Да, я имел в виду отмену стандартного поведения. Это может произойти случайно, как, например, нарушение ODR может произойти случайно. Решение состоит не в том, чтобы использовать методы, которые скрывают, когда происходят такие аварии, а в том, чтобы использовать методы, которые, скорее всего, заставят их взорваться настолько громко, насколько это возможно, чтобы ошибки могли быть замечены и исправлены как можно раньше.
bames53

106

Важно помнить, что ваши заголовки не должны зависеть от других заголовков, включаемых в первую очередь. Один из способов обеспечить это - включить заголовки перед любыми другими заголовками.

В частности, это упоминается в статье «Мышление в C ++», ссылаясь на «Крупномасштабное проектирование программного обеспечения C ++» Лакоса:

Скрытых ошибок использования можно избежать, если обеспечить, чтобы файл .h компонента анализировался сам по себе - без предоставленных извне объявлений или определений ... Включение файла .h в качестве самой первой строки файла .c гарантирует, что не будет критической части информация, присущая физическому интерфейсу компонента, отсутствует в файле .h (или, если таковой имеется, вы узнаете об этом, как только попытаетесь скомпилировать файл .c).

То есть включить в следующем порядке:

  1. Заголовок прототипа / интерфейса для этой реализации (т. Е. Файл .h / .hh, соответствующий этому файлу .cpp / .cc).
  2. Другие заголовки из того же проекта, по мере необходимости.
  3. Заголовки из других нестандартных, несистемных библиотек (например, Qt, Eigen и т. Д.).
  4. Заголовки из других «почти стандартных» библиотек (например, Boost)
  5. Стандартные заголовки C ++ (например, iostream, функционал и т. Д.)
  6. Стандартные заголовки C (например, cstdint, dirent.h и т. Д.)

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

Руководство по стилю Google C ++ утверждает почти обратное, безо всякого обоснования; Я лично склоняюсь в пользу подхода Лакоса.


13
На данный момент Google C ++ Style Guide рекомендует сначала включить связанный заголовочный файл, следуя предложению Лакоса.
Филипп Бартек

Вы не получите ничего, кроме первого связанного заголовка, потому что, как только вы начнете включать заголовки в проекте, вы получите множество системных зависимостей.
Михей

@Micah - внутрипроектные заголовки, тянущие «много системных зависимостей», - это плохой дизайн, именно то, чего мы пытаемся здесь избежать. Дело в том, чтобы избежать как ненужных включений, так и неразрешенных зависимостей. Все заголовки должны быть включены без предварительного включения других заголовков. В случае, когда заголовок в проекте нуждается в системной зависимости, так и будет - тогда вы не будете (и не должны) включать системную зависимость после нее, если только код, локальный для этого файла, не использует вещи из этой системной системы хранения. Вы не можете и не должны полагаться на заголовки (даже на свои), чтобы включить системные deps, которые вы используете.
Натан Пол Симонс

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
Что касается вашего первого предложения «не полагаться на пользователей», как насчет предварительных объявлений в заголовочном файле, которые не нуждаются в том, чтобы включать заголовочный файл? Должны ли мы по-прежнему включать файл заголовка, потому что предварительные объявления возлагают ответственность на пользователя файла заголовка, чтобы включить соответствующие файлы.
Zoso

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который включает его первым (действительно, в моем личном коде связанный с ним модульный тест сначала включает его, а исходный код включает его в свою законную группу ). Что касается отсутствия влияния, если любой из заголовков включает в себя, <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. Платформа / OS / другие файлы заголовков (например, 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

Это не субъективно. Удостоверьтесь, что ваши заголовки не полагаются на то, что они находятся #includeв определенном порядке. Вы можете быть уверены, что не имеет значения, в каком порядке вы включаете заголовки 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. Это в свою очередь гарантирует, что заголовки могут быть включены в любом порядке их зависимыми.

Пример:

source1.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 ++, с таким количеством элементов, выходящих за рамки стандарта.

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

Мои идеи таковы: если во всех этих заголовках нет конфликта символов, любой порядок в порядке, и проблему с зависимостью заголовка можно исправить позже, добавив строки #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.