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

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

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

Ответ 1

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

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

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

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

Ответ 2

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

В частности, это упоминается в статье "Размышление в 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++ утверждает почти обратное, на самом деле безо всякого обоснования; Я лично склоняюсь в пользу подхода Лакоса.

Ответ 3

Я следую двум простым правилам, которые избегают подавляющего большинства проблем:

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

Я также следую рекомендациям:

  • Сначала включить заголовки систем (stdio.h и т.д.) с разделительной линией.
  • Группируйте их логически.

Другими словами:

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

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

Хотя, будучи руководством, это субъективная вещь. Правила, с другой стороны, я строго соблюдаю, даже до того, что вы предоставляете заголовочные файлы "wrapper" с включенными защитными устройствами и сгруппированными, включает в себя, если какой-то неприятный сторонний разработчик не подписывается на мое видение: -)

Ответ 4

Чтобы добавить свой кирпич к стене.

  • Каждый заголовок должен быть самодостаточным, который может быть протестирован только в том случае, если он включен сначала по крайней мере один раз
  • Нельзя ошибочно модифицировать значение стороннего заголовка, введя символы (макрос, типы и т.д.).

Итак, я обычно хожу так:

// 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 (проверка работоспособности)
  • Заголовки системы
  • Сторонние заголовки, упорядоченные по порядку зависимости
  • Заголовки проектов
  • Закрытые заголовки проекта

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

Ответ 5

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

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

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

Ответ 6

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

  • Заголовок для модуля .cc, который вы создаете. (Помогает обеспечить, чтобы каждый заголовок в вашем проекте не имел неявных зависимостей от других заголовков в вашем проекте.)
  • Системные файлы C.
  • Системные файлы С++.
  • Платформа/OS/другие файлы заголовков (например, win32, gtk, openGL).
  • Другие файлы заголовков из вашего проекта.

И, конечно, алфавитный порядок в каждом разделе, где это возможно.

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

Ответ 7

Это не субъективно. Убедитесь, что ваши заголовки не полагаются на #include d в определенном порядке. Вы можете быть уверены, что неважно, в каком порядке вы включаете заголовки STL или Boost.

Ответ 8

Сначала включите заголовок, соответствующий .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.

Ответ 9

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

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

Ответ 10

Это трудный вопрос в мире 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 будет утверждать ошибку переопределения макроса.

Мы молимся не встречаться с такими случаями в нашей карьере.