Обеспечение того, что заголовки явно включены в файл CPP

9

Я думаю, что это обычно хорошая практика для #includeзаголовка для любых типов, используемых в файле CPP, независимо от того, что уже включено через файл HPP. Так что я мог бы #include <string>, например, и в моем HPP, и в CPP, даже если бы я мог компилировать, если пропустил его в CPP. Таким образом, мне не нужно беспокоиться о том, использовала ли моя ГЭС предварительную декларацию или нет.

Есть ли инструменты, которые могут обеспечить этот #includeстиль кодирования? Должен ли я применять этот стиль кодирования?

Поскольку препроцессор / компилятор не заботится о том #include, исходит ли он от HPP или CPP, я не получу обратной связи, если забуду следовать этому стилю.

М. Дадли
источник

Ответы:

7

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

Дэвид Хаммен
источник
2

Должен ли я применять этот стиль кодирования?

Возможно нет. Мое правило таково: включение заголовочного файла не может зависеть от порядка.

Вы можете легко проверить это с помощью простого правила, что первый файл, включенный в xc - это xh

Кевин Клайн
источник
1
Вы можете остановиться на этом? Я не понимаю, как это могло бы подтвердить независимость заказа.
детально
1
на самом деле он не гарантирует независимость заказа - он просто гарантирует, что #include "x.h"будет работать без каких-либо предварительных действий #include. Этого достаточно, если вы не злоупотребляете #define.
Кевин Клайн
1
А ну понятно. Все еще хорошая идея тогда.
детально
2

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

Если вы используете IDE, вы все равно сможете выполнить это без make-файла (в зависимости от вашей IDE). Просто создайте дополнительный проект, добавьте заголовочные файлы, которые вы хотите проверить, и измените настройки, чтобы скомпилировать его как файл C или C ++. Например, в MSVC вы должны изменить настройку «Тип элемента» в «Свойства конфигурации-> Общие».

Капитан Облиус
источник
1

Я не думаю, что такой инструмент существует, но я был бы рад, если какой-то другой ответ опровергает меня.

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

Единственный способ, которым мог бы работать такой инструмент, - это если он мог бы сбросить свою таблицу символов только на содержимое файла заголовка, который он обработал, но затем вы столкнулись с проблемой, заключающейся в том, что заголовки, которые формируют внешний API библиотеки, делегируют фактические объявления внутренние заголовки.
Например, <string>в GCC реализация libc ++ ничего не объявляет, а просто включает в себя набор внутренних заголовков, которые содержат фактические объявления. Если инструмент сбрасывает свою таблицу символов на то, что было объявлено <string>само по себе, это ничего не значит.
Вы могли бы иметь инструмент различать между #include ""и #include <>, но это не поможет вам, если внешняя библиотека использует #include ""для включения своих внутренних заголовков в API.

Барт ван Инген Шенау
источник
1

не #Pragma onceдостигает этого? Вы можете включать что-либо столько раз, сколько хотите, либо напрямую, либо через цепочку включений, и до тех пор, пока #Pragma onceрядом с каждым из них есть, заголовок включается только один раз.

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

Ericson2314
источник
1

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

Gvozden
источник
Можете ли вы объяснить, как это "значительно сократит время компиляции"?
Mawg говорит восстановить Монику
1
Это гарантирует, что каждый модуль компиляции (файл CPP) извлекает только минимум включаемых файлов во время компиляции. В противном случае, если вы добавляете включения в H-файлы, цепочки ложных зависимостей быстро формируются, и вы в конечном итоге извлекаете все включения с каждой компиляцией.
Гвозден
С включенной защитой я мог бы задаться вопросом «значительно», но я полагаю, что доступ к диску (в порядке обнаружения этих нечувствительных датчиков) «медленный», поэтому уступлю. Спасибо за разъяснения
Mawg говорит восстановить Монику
0

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

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

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

Дима
источник
3
Если вы оставляете включенные из заголовков, то порядок включения начинает иметь значение ... и я определенно хочу этого избежать.
М. Дадли
1
-1: создает кошмар зависимости. Улучшение xh может потребовать изменения в КАЖДОМ файле .cpp, который включает xh
kevin cline
1
@ M.Dudley, как я уже говорил, есть свои плюсы и минусы.
Дима