Я работаю над большим проектом C++ в Visual Studio 2008, и есть много файлов с ненужными директивами #include. Иногда #include - это просто артефакты, и все будет нормально компилироваться с их удалением, а в других случаях классы могут быть объявлены вперед, а #include может быть перемещен в файл .cpp. Есть ли какие-нибудь хорошие инструменты для обнаружения обоих этих случаев?





Хотя он не отображает ненужные включаемые файлы, в Visual Studio есть параметр /showIncludes (щелкните правой кнопкой мыши файл .cpp, Properties->C/C++->Advanced), который выводит дерево всех включенных файлов во время компиляции. Это может помочь в идентификации файлов, которые не нужно включать.
Вы также можете взглянуть на идиому pimpl, чтобы избавиться от меньшего количества зависимостей файлов заголовков, чтобы упростить просмотр мусора, который вы можете удалить.
Откуда этот выход? Кроме того, чтобы ускорить работу будущих читателей, вам нужно щелкнуть правой кнопкой мыши файл в проводнике решений.
Как и Тиммерманс, я не знаком ни с какими инструментами для этого. Но я знал программистов, которые написали сценарий Perl (или Python), чтобы попытаться закомментировать каждую строку включения по одной, а затем скомпилировать каждый файл.
Похоже, что теперь Эрик Раймонд есть инструмент для этого.
cpplint.py Google имеет правило «включать то, что вы используете» (среди многих других), но, насколько я могу судить, нет «включать Только, что вы используете». Тем не менее, это может быть полезно.
Мне пришлось рассмеяться, когда я прочитал это. Мой босс проделал то же самое в одном из наших проектов в прошлом месяце. Уменьшенный заголовок учитывается несколькими факторами.
codewarrior на Mac раньше имел встроенный скрипт, чтобы сделать это, закомментировать, скомпилировать, при ошибке не комментировать, продолжить до конца #includes. Это работало только для #includes в верхней части файла, но обычно они там и находятся. Это не идеально, но сохраняет разум.
Я не знаю ни одного такого инструмента, и я думал о его написании в прошлом, но оказалось, что это трудная проблема для решения.
Скажем, ваш исходный файл включает a.h и b.h; a.h содержит #define USE_FEATURE_X, а b.h использует #ifdef USE_FEATURE_X. Если #include "a.h" закомментирован, ваш файл все еще может компилироваться, но может не делать то, что вы ожидаете. Обнаружить этот программно нетривиально.
Независимо от того, какой инструмент это делает, также необходимо знать вашу среду сборки. Если a.h выглядит так:
#if defined( WINNT )
#define USE_FEATURE_X
#endif
Тогда USE_FEATURE_X определяется только в том случае, если определен WINNT, поэтому инструмент должен знать, какие директивы генерируются самим компилятором, а также какие из них указаны в команде компиляции, а не в файле заголовка.
PC Lint отлично подходит для этого, и он также находит для вас множество других глупых проблем. У него есть параметры командной строки, которые можно использовать для создания внешних инструментов в Visual Studio, но я обнаружил, что с надстройкой Визуальный линт легче работать. Даже бесплатная версия Visual Lint помогает. Но попробуйте PC-Lint. Настройка его так, чтобы он не выдавал слишком много предупреждений, займет немного времени, но вы будете поражены тем, что он появится.
Некоторые инструкции о том, как это сделать с помощью pc-lint, можно найти на riverblade.co.uk/….
Добавление одного или обоих из следующих #defines исключит часто ненужные файлы заголовков и может существенно улучшить время компиляции, особенно если код не использует функции Windows API.
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
См. http://support.microsoft.com/kb/166474
Нет необходимости в обоих - VC_EXTRALEAN определяет WIN32_LEAN_AND_MEAN
Если вы хотите удалить ненужные файлы #include, чтобы сократить время сборки, лучше потратить ваше время и деньги на распараллеливание процесса сборки с помощью cl.exe / MP, сделать -j, Xoreax IncrediBuild, distcc / мороженое и т. д.
Конечно, если у вас уже есть параллельный процесс сборки, и вы все еще пытаетесь его ускорить, то во что бы то ни стало очистите свои директивы #include и удалите эти ненужные зависимости.
Некоторые из существующих ответов утверждают, что это сложно. Это действительно так, потому что вам нужен полноценный компилятор для обнаружения случаев, в которых было бы уместно предварительное объявление. Вы не можете разобрать C++, не зная, что означают символы; грамматика для этого слишком неоднозначна. Вы должны знать, является ли определенное имя именем класса (может быть объявлено вперед) или переменной (нельзя). Кроме того, вам нужно знать пространство имен.
Вы можете просто сказать: «Решение, какие #includes необходимы, эквивалентно решению проблемы остановки. Удачи :)» Конечно, вы можете использовать эвристику, но я не знаю ни одного бесплатного программного обеспечения, которое бы это делало.
!! ОТКАЗ !! Я работаю над коммерческим инструментом статического анализа (не PC Lint). !! ОТКАЗ !!
Есть несколько проблем с простым подходом без синтаксического анализа:
1) Наборы перегрузки:
Возможно, что перегруженная функция имеет объявления из разных файлов. Возможно, удаление одного файла заголовка приведет к выбору другой перегрузки, а не к ошибке компиляции! Результатом будет незаметное изменение семантики, которое потом будет очень трудно отследить.
2) Специализации шаблона:
Как и в примере с перегрузкой, если у вас есть частичные или явные специализации для шаблона, вы хотите, чтобы они все были видны при использовании шаблона. Возможно, специализации для основного шаблона находятся в разных файлах заголовков. Удаление заголовка со специализацией не вызовет ошибки компиляции, но может привести к неопределенному поведению, если эта специализация была бы выбрана. (См .: Видимость шаблонной специализации функции C++)
Как указывает msalters, выполнение полного анализа кода также позволяет анализировать использование классов. Проверяя, как класс используется через определенный путь к файлам, возможно, что определение класса (и, следовательно, все его зависимости) могут быть полностью удалены или, по крайней мере, перемещены на уровень ближе к основному источнику во включении. дерево.
@RichardCorden: Ваше программное обеспечение (QA C++) слишком дорогое.
@XanderTulip: Трудно ответить на это, не закончив коммерческой презентацией, поэтому заранее прошу прощения. ИМХО, вы должны учитывать, сколько времени потребуется хорошему инженеру, чтобы найти такие вещи (а также многие другие ошибки языка / потока управления) в любом проекте разумного размера. По мере изменения программного обеспечения одну и ту же задачу необходимо повторять снова и снова. Поэтому, когда вы рассчитываете сэкономленное время, стоимость инструмента, вероятно, не будет значительной.
Если вы еще этого не сделали, использование предварительно скомпилированного заголовка, включающего все, что вы не собираетесь изменять (заголовки платформы, внешние заголовки SDK или статические уже завершенные части вашего проекта), значительно изменит время сборки.
http://msdn.microsoft.com/en-us/library/szfdksca(VS.71).aspx
Кроме того, хотя для вашего проекта может быть уже слишком поздно, хорошей практикой является организация вашего проекта по разделам, а не объединение всех локальных заголовков в один большой основной заголовок, хотя это требует небольшой дополнительной работы.
Отличное объяснение предварительно скомпилированных заголовков: cygnus-software.com/papers/precompiledheaders.html (Не уверен, что автогенерация предварительно скомпилированных заголовков нарушена в последних версиях VisualStudio, но это стоит проверить.)
Начните с каждого включаемого файла и убедитесь, что каждый включаемый файл включает только то, что необходимо для его компиляции. Любые включаемые файлы, которые отсутствуют в файлах C++, могут быть добавлены в сами файлы C++.
Для каждого включаемого и исходного файла закомментируйте каждый включаемый файл по одному и посмотрите, компилируется ли он.
Также рекомендуется отсортировать включаемые файлы в алфавитном порядке, а если это невозможно, добавьте комментарий.
Я не уверен, насколько практичен этот комментарий, если задействовано очень большое количество файлов реализации.
Если ваши файлы заголовков обычно начинаются с
#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#endif
(в отличие от использования #pragma один раз) вы можете изменить это на:
#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#else
#pragma message("Someheader.h superfluously included")
#endif
А поскольку компилятор выводит имя компилируемого файла cpp, это позволит вам узнать, по крайней мере, какой файл cpp вызывает многократное добавление заголовка.
Я думаю, что можно включать заголовки несколько раз. Хорошо включать то, что вы используете, и не зависеть от ваших включаемых файлов. Я думаю, что OP хочет найти #includes, которые на самом деле не используются.
ИМО активно неправильное занятие. Заголовки должны включать другие заголовки, если они не работают без них. И когда у вас есть A.h и B.h, которые оба зависят от C.h, и вы включаете A.h и B.h, потому что вам нужны оба, вы буду включаете C.h дважды, но это нормально, потому что компилятор пропустит его во второй раз, а если вы этого не сделали, вам нужно помнить, что всегда нужно включать C.h, прежде чем A.h или B.h окажутся гораздо более бесполезными включениями.
Контент точный, это хорошее решение для поиска заголовков, которые включаются несколько раз. Однако на исходный вопрос это не отвечает, и я не могу представить, когда это будет хорошей идеей. Файлы cpp должны включать все заголовки, от которых они зависят, даже если заголовок включен раньше где-то еще. Вы не хотите, чтобы ваш проект был привязан к определенному порядку компиляции или предполагал, что другой заголовок будет включать тот, который вам нужен.
PC-Lint действительно может это сделать. Один из простых способов сделать это - настроить его так, чтобы он обнаруживал только неиспользуемые включаемые файлы и игнорировал все другие проблемы. Это довольно просто - чтобы включить только сообщение 766 («Заголовочный файл не используется в модуле»), просто включите параметры -w0 + e766 в командную строку.
Тот же подход можно использовать и со связанными сообщениями, такими как 964 («Заголовочный файл не используется напрямую в модуле») и 966 («Косвенно включаемый заголовочный файл не используется в модуле»).
FWIW Я написал об этом более подробно в сообщении в блоге на прошлой неделе на http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318.
Если вам интересна эта тема в целом, вы можете попробовать Lakos 'Разработка крупномасштабного программного обеспечения на C++. Это немного устарело, но связано с множеством проблем «физического дизайна», таких как поиск абсолютного минимума заголовков, которые необходимо включить. Я действительно не видел, чтобы подобные вещи обсуждались где-либо еще.
Попробуйте Включить менеджера. Он легко интегрируется в Visual Studio и визуализирует ваши пути включения, что помогает вам находить ненужные вещи. Внутри он использует Graphviz, но есть еще много интересных функций. И хотя это коммерческий продукт, у него очень низкая цена.
Вы можете построить граф включений, используя Наблюдатель за зависимостями файлов C / C++, и визуально находить ненужные включения.
Возможно, немного поздно, но однажды я нашел Perl-скрипт WebKit, который делал именно то, что вы хотели. Думаю, потребуется некоторая адаптация (я плохо разбираюсь в perl), но это должно помочь:
(это старая ветка, потому что в стволе больше нет файла)
Для этого есть новый инструмент на основе Clang, включить то, что вы используете.
Обратите внимание, что это в настоящее время не собирается с clang 3.5
4 июня 2015 года был выпущен iwyu 0.4. Он совместим с llvm + clang 3.6 согласно домашняя страница iwyu.
Если вы будете работать с Eclipse CDT, вы можете попробовать http://includator.com для оптимизации вашей структуры включения. Однако Includator может не знать достаточно о предопределенных включениях VC++, и настройка CDT для использования VC++ с правильными включениями еще не встроена в CDT.
Если есть определенный заголовок, который, по вашему мнению, больше не нужен (скажем, string.h), вы можете закомментировать это include, а затем поместить его под всеми включает:
#ifdef _STRING_H_
# error string.h is included indirectly
#endif
Конечно, заголовки вашего интерфейса могут использовать другое соглашение #define для записи их включения в память CPP. Или без соглашения, и в этом случае этот подход не сработает.
Затем перестройте. Есть три возможности:
Строится нормально. string.h не был критичным для компиляции, и включение для него можно удалить.
Ошибка #error. string.g был включен как-то косвенно Вы все еще не знаете, требуется ли string.h. Если требуется, вы должен прямо # включить его (см. ниже).
У вас другая ошибка компиляции. string.h был нужен и не включен косвенно, поэтому включение было правильным с самого начала.
Обратите внимание, что в зависимости от косвенного включения, когда ваш .h или .c напрямую использует другой .h почти наверняка является ошибкой: вы фактически обещаете, что ваш код будет требовать только этот заголовок до тех пор, пока вы используете какой-либо другой заголовок требует, что, вероятно, не то, что вы имели в виду.
Предостережения, упомянутые в других ответах о заголовках, которые изменяют поведение скорее, здесь также применимо объявление вещей, которые вызывают сбои при сборке.
Последняя версия Jetbrains IDE, CLion, автоматически показывает (серым цветом) включения, которые не используются в текущем файле.
Также можно получить список всех неиспользуемых включений (а также функций, методов и т. д.) Из IDE.
/ showincludes великолепен. Делать это вручную без этого было непросто.