Подтвердить что ты не робот

Очистите свои #include заявления?

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

Созданы ли какие-либо инструменты для выявления или устранения проблем? Любые предложения?

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

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

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

4b9b3361

Ответ 1

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

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

Кроме того, в книге Lakos "Крупномасштабная разработка программного обеспечения на С++" (например) перечислены многие, многие методы для перемещения данных реализации из заголовка и в соответствующий файл 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 ...

Ответ 2

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

Например, класс 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

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

Ответ 3

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

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

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

  • dir2/foo2.h (предпочтительное расположение - см. подробности ниже).
  • Системные файлы C.
  • Системные файлы С++.
  • Файлы других библиотек.
  • Ваши файлы проекта .h.

Ответ 4

В зависимости от размера вашего проекта, глядя на диаграммы include, созданные Doxygen (с опцией INCLUDE_GRAPH), может быть полезным.

Ответ 5

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

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

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

Ответ 6

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

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

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

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

Ответ 7

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

Ответ 8

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

Ответ 9

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

Ответ 10

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

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

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

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

И особенно в функции #include:

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

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

Ответ 11

Если вы кодируете в Eclipse с CDT, вы можете использовать команду Organize Includes. Просто нажмите Ctrl + Shift + O, и он добавит необходимые вложения и удалит ненужные.

Ответ 12

Обычно я создаю один исходный файл (например, main.c) и один заголовочный файл для этого исходного файла (main.h). В исходном файле я помещаю все основные функции "интерфейса", которые я использую в этом файле (в основном это будет main()), а затем любые функции, которые я получаю после реорганизации этих функций (подробности реализации) идти ниже. В файле заголовка я объявляю некоторые функции extern, которые определены в других исходных файлах, но используются в исходном файле, который использует этот заголовок. Затем я объявляю любые структуры или другие типы данных, которые я использую в этом исходном файле.

Затем я просто компилирую и связываю их все вместе. Он остается красивым и чистым. Типичный раздел... в моем текущем проекте выглядит следующим образом

#include<windows.h>
#include<windowsx.h>
#include<stdio.h>

#include"interface.h"
#include"thissourcefile.h"

//function prototypes

//source

есть заголовок interface, который отслеживает структуры данных, которые я использую во всех формах проекта, а затем thissourcefile.h, который делает именно то, что я только что объяснил (объявляет externs и т.д.).

Кроме того, я никогда не определяю ничего в своих заголовках, я только размещаю там объявления. Таким образом, они могут быть включены в различные исходные файлы и по-прежнему успешно связаны. В заголовке идут прототипы функций (extern, static или other) и декларации, поэтому их можно использовать много раз - определения идут в источнике, потому что они должны быть только в одном месте.

Это было бы очевидно, если бы вы создавали библиотеку или что-то в этом роде. Но только для внутреннего связывания проектов я считаю, что это держит все красивым и чистым. Кроме того, если вы пишете файл makefile (или используете только IDE), компиляция действительно проста и эффективна.