Очистить ваши заявления #include? - PullRequest
26 голосов
/ 18 июня 2009

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

Вы создали какие-либо инструменты для выявления или устранения проблем? Есть предложения?

Я думал о том, чтобы написать что-то, что компилирует каждый файл без заголовка много раз, каждый раз удаляя оператор #include. Продолжайте делать это, пока не будет достигнут минимальный набор включений.

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

Прежде чем я что-то создал, я подумал, что должен спросить здесь. Это кажется несколько универсальной проблемой.

Ответы [ 12 ]

23 голосов
/ 18 июня 2009

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

Тот же эффект можно получить, применив следующее правило: первый файл заголовка, который должен включать foo .c или foo .cpp, должен иметь соответственно имя Foo .h. Это гарантирует, что foo .h включает все, что нужно для компиляции.

Кроме того, в книге Лакоса Крупномасштабный программный дизайн C ++ (например) перечислено много, много методов для перемещения деталей реализации из заголовка в соответствующий файл CPP. Если вы возьмете это до крайности, используя такие методы, как Cheshire Cat (который скрывает все детали реализации) и Factory (который скрывает существование подклассов), тогда многие заголовки смогут стоять в одиночестве, не включая другие заголовки, а вместо этого обходятся только вместо этого переадресуйте объявление непрозрачным типам ... за исключением, возможно, шаблонных классов.

В конце каждый заголовочный файл может содержать:

  • Нет файлов заголовков для типов, которые являются элементами данных (вместо этого элементы данных определяются / скрываются в файле CPP с использованием метода "cheshire cat" a.k.a. "pimpl")

  • Нет заголовочных файлов для типов, которые являются параметрами или возвращают типы из методов (вместо этого это предопределенные типы, такие как int; или, если они являются пользовательскими типами, то это ссылки, в которых если достаточно заранее объявленного, непрозрачного объявления типа, такого как просто class Foo; вместо #include "foo.h" в заголовочном файле).

Тогда вам нужен заголовочный файл для:

  • Суперкласс, если это подкласс

  • Возможно, любые шаблонные типы, которые используются в качестве параметров метода и / или возвращаемых типов: очевидно, вы должны быть в состоянии также заранее объявить классы шаблона, но некоторые реализации компилятора могут иметь проблемы с этим (хотя Вы также можете инкапсулировать любые шаблоны, например List<X>, в качестве подробностей реализации пользовательского типа, например ListX).

На практике я мог бы создать "standard.h", который включает все системные файлы (например, заголовки STL, специфичные для O / S типы и / или любые #define s и т. Д.), Которые используются любыми / всеми файлы заголовков в проекте, и включите их в качестве первого заголовка в каждый файл заголовка приложения (и скажите компилятору, что этот «standard.h» следует рассматривать как «предварительно скомпилированный файл заголовка»).


//contents of foo.h
#ifndef INC_FOO_H //or #pragma once
#define INC_FOO_H

#include "standard.h"
class Foo
{
public: //methods
  ... Foo-specific methods here ...
private: //data
  struct Impl;
  Impl* m_impl;
};
#endif//INC_FOO_H

//contents of foo.cpp
#include "foo.h"
#include "bar.h"
Foo::Foo()
{
  m_impl = new Impl();
}
struct Foo::Impl
{
  Bar m_bar;
  ... etc ...
};
... etc ...
11 голосов
/ 19 июня 2009

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

Например, класс 'Tetris' имеет файлы Tetris.h и Tetris.cpp.Порядок включения для Tetris.cpp будет

#include "Tetris.h"     // corresponding header first
#include "Block.h"      // ..then application level includes
#include "Utils/Grid.h" // ..then library dependencies
#include <vector>       // ..then stl
#include <windows.h>    // ..then system includes

И теперь я понимаю, что на самом деле это не отвечает на ваш вопрос, поскольку эта система на самом деле не помогает очистить ненужные включения.Ах, хорошо ..

6 голосов
/ 19 июня 2009

В зависимости от размера вашего проекта, может быть полезен просмотр включенных графиков, созданных doxygen (с включенной опцией INCLUDE_GRAPH).

6 голосов
/ 18 июня 2009

Обнаружение лишних включений уже обсуждалось в этом вопросе .

Мне неизвестны какие-либо инструменты, помогающие обнаруживать недостаточные, но встречающиеся в работе включения, но здесь могут помочь хорошие соглашения по кодированию. Например, Руководство по стилю Google C ++ предписывает следующее с целью уменьшения скрытых зависимостей:

В dir/foo.cc, основной целью которого является реализация или проверка содержимого в dir2/foo2.h, закажите ваши включения следующим образом:

  1. dir2/foo2.h (предпочтительное местоположение - подробности см. Ниже).
  2. Системные файлы C.
  3. Системные файлы C ++.
  4. Файлы других библиотек .h.
  5. Файлы вашего проекта .h.
5 голосов
/ 19 июня 2009

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

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

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

2 голосов
/ 16 февраля 2011

Если вы используете компилятор Visual Studio, вы можете попробовать / showInclude вариант компилятора, а затем проанализировать, что он излучает в stderr. MSDN: "Заставляет компилятор выводить список включаемых файлов. Также отображаются вложенные включаемые файлы (файлы, включенные из включаемых вами файлов)."

2 голосов
/ 19 июня 2009

Я думал о написании то, что компилирует каждый не заголовочный файл индивидуально много раз, каждый раз удаляя #include заявление. Продолжайте делать это до достигнут минимальный набор включений.

Я думаю, что это неверно, и приведет к тому, что наборы будут «недостаточными, но просто сработает».

Предположим, ваш исходный файл использует numeric_limits, но также включает в себя некоторый заголовочный файл, который по собственным причинам включает <limits>. Это не значит, что ваш исходный файл не должен содержать <limits>. Этот другой заголовочный файл, вероятно, не документирован для определения всего, что определено в <limits>, просто так происходит. Когда-нибудь это может прекратиться: возможно, он использует только одно значение в качестве параметра по умолчанию для некоторой функции, и, возможно, это значение по умолчанию изменяется с std::numeric_limits<T>::min() на 0. И теперь ваш исходный файл больше не компилируется, и сопровождающий этого Заголовочный файл даже не знал, что ваш файл существует, пока не сломал его сборку.

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

1 голос
/ 09 апреля 2012

Взгляните на проект cppclean . Хотя они еще не реализовали эту функцию, но это планируется сделать.

С сайта проекта:

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

Цель - найти проблемы, которые замедляют разработку в больших базах кода. которые изменяются со временем, оставляя неиспользуемый код. Этот код может войти множество форм от неиспользуемых функций, методов, членов данных, типов и т. д. до ненужные директивы #include. Ненужное #include может привести к значительные дополнительные компиляции увеличивают цикл edit-compile-run.

И, в частности, функция #include:

  • (планируется) Найти ненужные заголовочные файлы
    • Нет прямой ссылки на что-либо в шапке
    • Заголовок не нужен, если вместо этого были объявлены классы вперед
  • (планируется) Исходные файлы, которые ссылаются на заголовки, не включенные напрямую, #, т.е. файлы, которые зависят от переходного #include из другого заголовка

Здесь вы можете найти зеркало в BitBucket.

1 голос
/ 19 июня 2009

Что касается инструментов, я использовал Imagix (это было около 6 лет назад) в окнах для определения включений, которые не нужны, а также включений, которые необходимы, но косвенно включаются через другое включение.

1 голос
/ 18 июня 2009

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

...