Куда поместить операторы, заголовок или источник? - PullRequest
96 голосов
/ 15 октября 2010

Должен ли я включить включения в заголовочный файл или исходный файл?Если заголовочный файл содержит операторы включения, то если я включу этот заголовочный файл в свой источник, то будут ли в моем исходном файле все включенные файлы, которые были в моем заголовке?Или я должен просто включить их только в мой исходный файл?

Ответы [ 9 ]

121 голосов
/ 15 октября 2010

Помещает включения в заголовок, только если они нужны самому заголовку.

Примеры:

  • Ваша функция возвращает тип size_t. Затем #include <stddef.h> в заголовочном файле .
  • Ваша функция использует strlen. Затем #include <string.h> в файле source .
24 голосов
/ 15 октября 2010

На протяжении многих лет было много разногласий по этому поводу. Когда-то было традиционным, что заголовок only объявляет, что было в каком-либо модуле, с которым он связан, поэтому много заголовков предъявляли особые требования, что вы #include определенный набор заголовков (в определенном порядке). Некоторые чрезвычайно традиционные программисты на С все еще следуют этой модели (по крайней мере, в некоторых случаях неукоснительно).

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

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

11 голосов
/ 15 октября 2010

Ваши #include s должны иметь файлы заголовков, и каждый файл (источник или заголовок) должен #include файлы заголовков, которые ему нужны. Заголовочные файлы должны #include минимально необходимые заголовочные файлы, и исходные файлы также должны, хотя это не так важно для исходных файлов.

Исходный файл будет иметь заголовки #include s, заголовки #include и т. Д. Вплоть до максимальной глубины вложенности. Вот почему вам не нужны лишние #include s в заголовочных файлах: они могут привести к тому, что в исходном файле будет много заголовочных файлов, которые могут ему не понадобиться, что замедлит компиляцию.

Это означает, что вполне возможно, что заголовочные файлы могут быть включены дважды, и это может быть проблемой. Традиционный метод состоит в том, чтобы поместить «include guard» в заголовочные файлы, например, для файла foo.h:

#ifndef INCLUDE_FOO_H
#define INCLUDE_FOO_H
/* everything in header goes here */
#endif
4 голосов
/ 15 октября 2010

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

Еще одна вещь, которую следует учитывать, это то, что если функция берет указатель на структуру, можно написать прототип как

void foo(struct BAR_s *bar);

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

PS - во многих моих проектах будет файл, который, как ожидается, каждый модуль будет включать в себя #include, содержащий такие вещи, как typedefs для целых размеров и несколько общих структур и объединений [например,

typedef union {
  unsigned long l;
  unsigned short lw[2];
  unsigned char lb[4];
} U_QUAD;

(Да, я знаю, что у меня будут проблемы, если я перейду на архитектуру с прямым порядком байтов, но поскольку мой компилятор не допускает анонимные структуры в объединениях, использование именованных идентификаторов для байтов в объединении потребует быть доступным как theUnion.b.b1 и т. д., что выглядит довольно раздражающим.

4 голосов
/ 15 октября 2010

Подход, в котором я развивался более двадцати лет, таков:

Рассмотрим библиотеку.

Существует несколько файлов C, один внутренний файл H и один внешний файл H. Файлы C включают внутренний файл H. Внутренний файл H включает в себя внешний файл H.

Вы видите, что из компиляторов POV, когда он компилирует файл C, существует иерархия;

внешний -> внутренний -> код C

Это правильный порядок, поскольку то, что является внешним, - это все, что нужно третьей стороне для использования библиотеки. То, что является внутренним, требуется для компиляции кода C.

4 голосов
/ 15 октября 2010

Если заголовочный файл A #includes заголовочный файл B и C, то каждый исходный файл, который #includes A, также получит B и C #included.Препроцессор буквально просто выполняет подстановку текста: где бы он ни находил текст, который говорит #include <foo.h>, он заменяет его текстом foo.h файла.

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

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

3 голосов
/ 15 октября 2010

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

1 голос
/ 15 октября 2010

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

#ifndef NAME_OF_HEADER_H
#define NAME_OF_HEADER_H

...definition of header file...

#endif

Это предотвращает многократное включение заголовка, что приводит к ошибке компилятора.

1 голос
/ 15 октября 2010

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

Помните, что если вы включите этот заголовок в другие источники, они также получат включения из заголовка, что не всегда желательно. Вы должны включать вещи только там, где они используются.

...