Это один из тех стандартов кодирования, которые «должны», а не «должны». Причина в том, что вам пришлось бы написать синтаксический анализатор C ++ для его принудительного применения.
Очень распространенным правилом для заголовочных файлов является то, что они должны стоять самостоятельно. Заголовочный файл не должен требовать, чтобы некоторые другие заголовочные файлы были #included перед включением соответствующего заголовка. Это проверяемое требование. Учитывая некоторый случайный заголовок foo.hh
, следующее должно скомпилироваться и выполнить:
#include "foo.hh"
int main () {
return 0;
}
Это правило имеет последствия в отношении использования других классов в некотором заголовке. Иногда этих последствий можно избежать, объявив другие классы вперёд. Это невозможно с большим количеством стандартных библиотечных классов. Нет способа переслать объявление экземпляра шаблона, например std::string
или std::vector<SomeType>
. Вы должны использовать #include
эти заголовки STL в заголовке, даже если тип используется только в качестве аргумента функции.
Другая проблема связана с вещами, которые вы случайно перетаскиваете. Пример: рассмотрите следующее:
файл foo.cc:
#include "foo.hh"
#include "bar.hh"
void Foo::Foo () : bar() { /* body elided */ }
void Foo::do_something (int item) {
...
bar.add_item (item);
...
}
Вот bar
член класса Foo
данных, который имеет тип Bar
. Вы сделали все правильно, и у вас есть #included bar.hh, хотя это должно было быть включено в заголовок, который определяет класс Foo
. Тем не менее, вы не включили материал, используемый Bar::Bar()
и Bar::add_item(int)
. Во многих случаях эти вызовы могут привести к дополнительным внешним ссылкам.
Если вы проанализируете foo.o
с помощью такого инструмента, как nm
, то окажется, что функции foo.cc
вызывают все виды вещей, для которых вы не сделали соответствующего #include
. Так что вы должны добавить #include
директивы для этих внешних ссылок foo.cc
? Ответ абсолютно нет. Проблема в том, что очень трудно отличить те функции, которые вызываются случайно, от тех, которые вызываются напрямую.
#include "x.h"
будет работать без каких-либо предварительных действий#include
. Этого достаточно, если вы не злоупотребляете#define
.Если вам необходимо применить правило, согласно которому отдельные заголовочные файлы должны стоять самостоятельно, вы можете использовать уже имеющиеся у вас инструменты. Создайте основной make-файл, который компилирует каждый заголовочный файл отдельно, но не генерирует объектный файл. Вы сможете указать, в каком режиме компилировать файл заголовка (режим C или C ++), и убедиться, что он может работать самостоятельно. Вы можете сделать разумное предположение, что выходные данные не содержат ложных срабатываний, все необходимые зависимости объявлены и выходные данные являются точными.
Если вы используете IDE, вы все равно сможете выполнить это без make-файла (в зависимости от вашей IDE). Просто создайте дополнительный проект, добавьте заголовочные файлы, которые вы хотите проверить, и измените настройки, чтобы скомпилировать его как файл C или C ++. Например, в MSVC вы должны изменить настройку «Тип элемента» в «Свойства конфигурации-> Общие».
источник
Я не думаю, что такой инструмент существует, но я был бы рад, если какой-то другой ответ опровергает меня.
Проблема написания такого инструмента заключается в том, что он очень легко сообщает о ложном результате, поэтому я оцениваю чистое преимущество такого инструмента как близкое к нулю.
Единственный способ, которым мог бы работать такой инструмент, - это если он мог бы сбросить свою таблицу символов только на содержимое файла заголовка, который он обработал, но затем вы столкнулись с проблемой, заключающейся в том, что заголовки, которые формируют внешний API библиотеки, делегируют фактические объявления внутренние заголовки.
Например,
<string>
в GCC реализация libc ++ ничего не объявляет, а просто включает в себя набор внутренних заголовков, которые содержат фактические объявления. Если инструмент сбрасывает свою таблицу символов на то, что было объявлено<string>
само по себе, это ничего не значит.Вы могли бы иметь инструмент различать между
#include ""
и#include <>
, но это не поможет вам, если внешняя библиотека использует#include ""
для включения своих внутренних заголовков в API.источник
не
#Pragma once
достигает этого? Вы можете включать что-либо столько раз, сколько хотите, либо напрямую, либо через цепочку включений, и до тех пор, пока#Pragma once
рядом с каждым из них есть, заголовок включается только один раз.Что касается его применения, то, возможно, вы могли бы создать систему сборки, которая просто включает каждый заголовок отдельно с некоторой фиктивной основной функцией, просто для обеспечения ее компиляции.
#ifdef
цепочка включает в себя для достижения наилучших результатов с этим методом тестирования.источник
Всегда включайте заголовочные файлы в файл CPP. Это не только значительно сокращает время компиляции, но и избавляет вас от многих проблем, если вы решите использовать предварительно скомпилированные заголовки. По моему опыту, даже выполнение задачи предварительных деклараций стоит практики. Нарушать правило только при необходимости.
источник
Я бы сказал, что у этой конвенции есть как преимущества, так и недостатки. С одной стороны, приятно знать, что именно включает в себя ваш файл .cpp. С другой стороны, список включений может легко увеличиться до смешного размера.
Один из способов поощрения этого соглашения - не включать что-либо в ваши собственные заголовки, а только в файлы .cpp. Тогда любой файл .cpp, использующий ваш заголовок, не будет компилироваться, если вы явно не включите все остальные заголовки, от которых он зависит.
Вероятно, здесь есть какой-то разумный компромисс. Например, вы можете решить, что можно включать стандартные заголовки библиотеки в свои собственные заголовки, но не более.
источник