Вопрос по c++, header, file – Очистить ваши заявления #include?

26

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

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

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

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

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

Почему бы просто не скомпилировать сам заголовочный файл? Если ваш компилятор поддерживает это, сделайте это, ничего не выводя (например, gcc & lt; s -fsyntax-only). CB Bailey
Хороший план, если у вас нет условной компиляции в вашем источнике или заголовках - ваши тесты могут не дать полного покрытия кода. Mark Ransom
Смотрите также:stackoverflow.com/questions/74326/… а такжеstackoverflow.com/questions/614794/… Eclipse

Ваш Ответ

12   ответов
0

вы можете использовать команду «Организовать включения». Просто нажмите Ctrl + Shift + O, и он добавит необходимые включения и удалит ненужные.

23

I would creating a source file that all it does is include a header file and try to compile it. If the compile fails, then the header file itself is missing an include.

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

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

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

No header files for types which are data members (instead, data members are defined/hidden in the CPP file using the "cheshire cat" a.k.a. "pimpl" technique)

No header files for types which are parameters to or return types from methods (instead, these are predefined types like int; or, if they're user-defined types, then they're references in which case a forward-declared, opaque type declaration like merely class Foo; instead of #include "foo.h" in the header file is sufficient).

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

The superclass, if this is a subclass

Possibly any templated types which are used as method parameters and/or return types: apparently you're supposed to be able to forward-declare template classes too, but some compiler implementations may have a problem with that (though you could also encapsulate any templates e.g. List<X> as implementation details of a user-defined type e.g. ListX).

На практике я мог бы сделать "standard.h" который включает в себя все системные файлы (например, заголовки STL, специфичные для O / S типы и / или любые другие#defines и т. д.), которые используются любыми / всеми заголовочными файлами в проекте, и включают его в качестве первого заголовка в каждый заголовочный файл приложения (и сообщают компилятору трактовать этот «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 ...
Вы можете заранее объявить классы шаблонов, если вы не можете заранее объявить классы шаблонов стандартной библиотеки, потому что это неопределенное поведение.
Обратите внимание, что если вы перемещаетесь между реализациями, то вы можете обнаружить, что разные стандартные заголовки включают (или не включают) заголовки, которые на самом деле нужны вашему проекту. Это неприятно, но санкционировано стандартом C ++, поскольку любой из стандартных заголовков может включать любые другие.
Это правильно. Но было бы неправильно просто включать заголовочные файлы, если вы можете избежать использования предварительных объявлений. Таким образом, для любых возвращаемых значений, параметров и типов указателей заголовки для этих типов не должны включаться, даже если компиляция завершится неудачно без предварительного объявления типов.
Теперь, когда я думаю об этом, вы абсолютно правы. Круто то, что я делал это годами и годами, не задумываясь о том, почему. Мне нравится, когда я по невежеству поступаю правильно. criddell
Это здравый совет, кодифицированный Центром космических полетов имени Годдарда НАСА в их стандартах кодирования на C и C ++.
6

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

6

этот вопрос.

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

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

dir2/foo2.h (preferred location — see details below). C system files. C++ system files. Other libraries' .h files. Your project's .h files.
5

остоит в том, что это может привести к все еще компиляции, но неправильному или неэффективному коду.

Template specialization: If you have a template specialization for a specific type that is in one header and the more general template in another, removing the specialization may leave the code in a compilable state, but with undesired results.

Overload resolution: A similar issue - if you have two overloads of one function in different headers, but that take somewhat compatible types, you can end up removing the version that is the better fit in one case, but still have the code compile. This is probably less likely than the template specialization version, but it is possible.

3. Компиляция кода, в которой пропущены объявления для функций, определенных в этом файле, изначально все нормально, но это означает, что объявления функций и сама функция могут быть не синхронизированы, и никто об этом не заметит. Я столкнулся с этим с удалением & amp; техника перекомпиляции - но обнаружил, что я мог бы использовать sparse (gcc wrapper) для обнаружения этих случаев.
-1

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

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

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

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

//function prototypes

//source

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

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

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

1

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

Что ж, я полагаю, что OP на самом деле не спрашивал о множественных включениях, но возникает вопрос "Как вы поддерживаете операторы #include в своем проекте C или C ++? [. , .] Вы создали какие-либо инструменты для выявления или устранения проблем? Есть предложения? & Quot; Я не знаю, почему за это проголосовали
Я использую #pragma один раз, но я думаю, что это почти то же самое на большинстве современных компиляторов. Я предпочитаю, потому что он меньше печатает и менее подвержен ошибкам, чем попытка. criddell
препроцессор не ограничивает вас включением заголовочного файла только один раз. Это тот факт, что каждый заголовочный файл, созданный здравомыслящим человеком, окружен тегом & lt; pre & gt; #ifndef MY_HEADER_H #define MY_HEADER_H // код заголовка #endif & lt; / pre & gt;
10

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

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

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

2

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

1

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

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

CppClean attempts to find problems in C++ source that slow development particularly in large code bases. It is similar to lint; however, CppClean focuses on finding global inter-module problems rather than local problems similar to other static analysis tools.

The goal is to find problems that slow development in large code bases that are modified over time leaving unused code. This code can come in many forms from unused functions, methods, data members, types, etc to unnecessary #include directives. Unnecessary #includes can cause considerable extra compiles increasing the edit-compile-run cycle.

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

(planned) Find unnecessary header files #included No direct reference to anything in the header Header is unnecessary if classes were forward declared instead (planned) Source files that reference headers not directly #included, ie, files that rely on a transitive #include from another header

Вот Вы можете найти зеркало в BitBucket.

Это похоже на отличный проект. Спасибо за указатель. criddell
2

something that compiles each non-header file individually many times, each time removing an #include statement. Continue doing this until a minimal set of includes is achieved.

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

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

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

Да, я полагаю, что с системными заголовками вы не заботитесь о включении ненужных вещей. Он может быть предварительно скомпилирован, и в любом случае вы никогда не меняете их, поэтому вы не провоцируете ненужные перестройки. На крупных проектах, конечно, этоreally плохая идея иметь любой такой "заголовок doom" это включает в себя все пользовательские заголовки.
Это особенно относится к системным заголовкам. Я мог бы сделать & quot; standard.h & quot; который включает в себя все системные файлы (например, заголовки STL), используемые любым файлом в проекте, и включает его в качестве первого заголовка в каждый файл заголовка приложения (и указывает компилятору трактовать этот «standard.h» как предварительно скомпилированный » заголовочный файл ").
1

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

Похожие вопросы