Как я могу обнаружить ненужные файлы #include в большом проекте на С++?

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

Ответ 1

Пока он не отображает ненужные файлы include, Visual studio имеет параметр /showIncludes (щелкните правой кнопкой мыши по файлу .cpp, Properties->C/C++->Advanced), который выведет дерево всех включенных файлов во время компиляции. Это может помочь в определении файлов, которые не нужно включать.

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

Ответ 2

PC Lint работает достаточно хорошо для этого, и он находит для вас всевозможные другие проблемные проблемы. Он имеет параметры командной строки, которые можно использовать для создания внешних инструментов в Visual Studio, но я обнаружил, что Visual Lint addin легче работать. Даже бесплатная версия Visual Lint помогает. Но дайте PC-Lint выстрел. Конфигурируя его, чтобы он не выдавал слишком много предупреждений, требуется немного времени, но вы будете поражены тем, что он появляется.

Ответ 3

!! ОТКАЗ!! Я работаю над инструментом коммерческого статического анализа (не PC Lint).!! ОТКАЗ!!

Существует несколько проблем с простым неанализирующим подходом:

1) Наборы перегрузки:

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

2) Специализация шаблонов:

Подобно примеру перегрузки, если у вас есть частичная или явная специализация для шаблона, вы хотите, чтобы все они были видимыми при использовании шаблона. Может быть, специализации для основного шаблона находятся в разных файлах заголовков. Удаление заголовка со специализацией не приведет к ошибке компиляции, но может привести к поведению undefined, если эта специализация будет выбрана. (См. Видимость специализированной специализации функции С++)

Как указано в "msalters", выполнение полного анализа кода также позволяет анализировать использование класса. Проверяя, как класс используется по конкретному пути к файлам, возможно, что определение класса (и, следовательно, всех его зависимостей) может быть полностью или хотя бы перенесено на уровень, близкий к основному источнику в include дерево.

Ответ 4

Здесь создан новый инструмент, основанный на Clang, include-what-you-use, который предназначен для этого.

Ответ 5

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

Скажите, что ваш исходный файл содержит 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, поэтому инструмент должен знать, какие директивы генерируются самим компилятором, а также какие из них указаны в команде компиляции, а не в файле заголовка.

Ответ 6

Как и Timmermans, я не знаком с инструментами для этого. Но я знал программистов, которые написали Perl (или Python) script, чтобы попробовать комментировать каждую из них, включая строку по одному, а затем скомпилировать каждый файл.


Похоже, что сейчас у Эрика Раймонда есть инструмент для этого.

Google cpplint.py имеет правило "включить то, что вы используете" (среди многих других), но, насколько я могу судить, no "включить только, что вы используете." Тем не менее, это может быть полезно.

Ответ 7

Если вам интересна эта тема в целом, вы можете проверить Lacos Large Scale С++ Разработка программного обеспечения. Он немного устарел, но в него входят многие проблемы "физического дизайна", такие как поиск абсолютного минимума заголовков, которые необходимо включить. Я действительно не видел такого рода вещи, которые обсуждались где-либо еще.

Ответ 8

Если ваши файлы заголовков обычно начинаются с

#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 вызывает заголовок, который нужно вносить несколько раз.

Ответ 9

Дайте Включить менеджер. Он легко интегрируется в Visual Studio и визуализирует ваши пути включения, которые помогают вам находить ненужные вещи. Внутри он использует Graphviz, но есть еще много интересных функций. И хотя это коммерческий продукт, он имеет очень низкую цену.

Ответ 11

PC-Lint действительно может это сделать. Один простой способ сделать это - настроить его для обнаружения только неиспользуемых файлов include и игнорировать все другие проблемы. Это довольно просто - включить только сообщение 766 ( "Файл заголовка не используется в модуле" ), просто включите в командной строке опции -w0 + e766.

Такой же подход можно использовать и для связанных сообщений, таких как 964 ( "Файл заголовка напрямую не используется в модуле" ) и 966 ( "Косвенный заголовочный файл, который не используется в модуле" ).

FWIW Я писал об этом более подробно в блоге на прошлой неделе в http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318.

Ответ 12

Если вы хотите удалить ненужные файлы #include, чтобы уменьшить время сборки, ваше время и деньги можно потратить на распараллеливание процесса сборки с помощью cl.exe/MP, make -j, Xoreax IncrediBuild, distcc/icecream и т.д.

Конечно, если у вас уже есть параллельный процесс сборки, и вы все еще пытаетесь его ускорить, тогда обязательно очистите свои директивы #include и удалите ненужные зависимости.

Ответ 13

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

Для каждого файла include и source, закомментируйте каждый из них каждый файл по одному и посмотрите, компилируется ли он.

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

Ответ 14

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

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

См. http://support.microsoft.com/kb/166474

Ответ 15

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

http://msdn.microsoft.com/en-us/library/szfdksca(VS.71).aspx

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

Ответ 16

Если вы будете работать с Eclipse CDT, вы можете попробовать http://includator.com, чтобы оптимизировать свою структуру включения. Тем не менее, Includator может не знать достаточно о предопределенных включениях VС++ и настройке CDT для использования VС++ с правильными включениями еще не встроен в CDT.

Ответ 17

Последняя версия Jetbrains IDE, CLion, автоматически отображает (в сером цвете) включенные, которые не используются в текущем файле.

Также возможно иметь список всех неиспользуемых включений (а также функций, методов и т.д.) из среды IDE.

Ответ 18

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

Ответ 19

Возможно, немного поздно, но однажды я нашел WebKit perl script, который сделал именно то, что вы хотели. Мне понадобится адаптация, я верю (я не очень разбираюсь в perl), но он должен сделать трюк:

http://trac.webkit.org/browser/branches/old/safari-3-2-branch/WebKitTools/Scripts/find-extra-includes

(это старая ветка, потому что у trunk больше нет файла)

Ответ 20

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

#ifdef _STRING_H_
#  error string.h is included indirectly
#endif

Конечно, ваши заголовки интерфейса могут использовать другое соглашение #define для записи их включения в память CPP. Или нет конвенции, и в этом случае этот подход не будет работать.

Затем перестройте. Существует три возможности:

  • Он строит нормально. string.h не был критичным для компиляции, а include для него могут быть удалены.

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

  • Вы получаете другую ошибку компиляции. string.h необходимо и не косвенно, поэтому включение было правильным для начала.

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

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