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

Почему включение заголовочного файла - это зло?

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

  • скорость компиляции
  • сокращение сложности управления файлами заголовков
  • удаление циклических зависимостей

Исходя из фона .net, я считаю, что управление заголовками расстраивает. У меня такое чувство, что мне нужно овладеть декларациями вперед, но до сих пор я общался.

Почему компилятор не работает для меня и не определяет мои зависимости, используя один механизм (включая)?

Как форвардные объявления ускоряют компиляции, так как в какой-то момент нужно будет скомпилировать объект, на который ссылается?

Я могу купить аргумент по уменьшенной сложности, но что будет практическим примером этого?

4b9b3361

Ответ 1

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

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

Вы можете увидеть, сколько, например, с помощью gcc -E:

Один #include <iostream> дает мои g++ 4.5.2 дополнительные 18 560 строк кода для обработки.

A #include <boost/asio.hpp> добавляет еще 74 906 строк.

A #include <boost/spirit/include/qi.hpp> добавляет 154 024 строки, что более 5 МБ кода.

Это добавляет, особенно если небрежно включаться в какой-либо файл, который включен в каждый файл вашего проекта.

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

Ответ 2

Почему компилятор не работает для меня и не определяет мои зависимости, используя один механизм (включая)?

Это невозможно, потому что, в отличие от некоторых других языков, С++ имеет неоднозначную грамматику:

int f(X);

Является ли это объявлением функции или определением переменной? Чтобы ответить на этот вопрос, компилятор должен знать, что означает X, поэтому перед этой строкой должно быть указано X.

Ответ 3

Потому что, когда вы делаете что-то вроде этого:

bar.h:

class Bar {
  int foo(Foo &);
}

Тогда компилятору не нужно знать, как определена структура/класс Foo; поэтому импорт заголовка, который определяет Foo, бесполезен. Кроме того, импортирование заголовка, который определяет Foo, также может потребовать импорта заголовка, который определяет некоторый другой класс, который использует Foo; и это может означать импорт заголовка, который определяет некоторый другой класс и т.д. черепахи полностью.

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

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

Ответ 4

Как форвардные объявления ускоряют компиляции, так как в какой-то момент нужно будет скомпилировать объект, на который ссылается?

1) уменьшенный диск i/o (меньше файлов для открытия, меньше)

2) сокращение использования памяти/процессора большинству переводов требуется только имя. если вы используете/выделяете объект, вам понадобится его объявление.

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

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

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

в том числе лениво, только добавляет значительный объем дискового/cpu/памяти, который превращается в невыносимые времена сборки для вас, в то же время вводя значительные зависимости (в нетривиальных проектах).

Я могу купить аргумент для уменьшения сложности, но какой практический пример этого будет?

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

Lakos написал хорошую книгу, которая подробно описывает это:

http://www.amazon.com/Large-Scale-Software-Design-John-Lakos/dp/0201633620/ref=sr_1_1?ie= UTF8 & s = книги & QID = 1304529571 & ср = 8-1

Ответ 6

Я использовал форвардные объявления просто для уменьшения объема навигации между исходными файлами. например если модуль X вызывает некоторый клей или функцию интерфейса F в модуле Y, то использование форвардного объявления означает запись функции, и вызов может быть выполнен только при посещении 2-х мест, Xc и Yc не столько проблема, когда хорошая среда IDE помогает вы ориентируетесь, но я предпочитаю кодирование снизу вверх, создавая рабочий код, а затем выясняя, как его обертывать, а не через спецификацию интерфейса сверху вниз. Поскольку сами интерфейсы развивают его, не нужно записывать их полностью.

В классах C (или С++ минус) можно по-настоящему сохранить детали структуры Private, только определяя их в исходных файлах, которые их используют, и только выставляя вперед объявления во внешний мир - уровень черного бокса, уничтожая виртуальные машины в С++/classes. Также можно избежать необходимости прототипа вещей (посещение заголовка), указав "снизу вверх" в исходных файлах (старое старое статическое ключевое слово).

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

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

Зависимости модулей

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

Ответ 7

Я считаю, что форвардные декларации ускоряют компиляцию, потому что заголовочный файл ТОЛЬКО включен там, где он фактически используется. Это уменьшает необходимость открытия и закрытия файла один раз. Вы правы, что в какой-то момент ссылка на объект должна быть скомпилирована, но если я использую только указатель на этот объект в другом файле .h, зачем его включать? Если я скажу компилятору, я использую указатель на класс, чтобы все, что ему нужно (до тех пор, пока я не вызываю какие-либо методы в этом классе.)

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

Мы практикуем это в моем источнике занятости. Мой босс объяснил это аналогичным образом, но я уверен, что его объяснение было более ясным.

Ответ 8

Как форвардные объявления ускоряют компиляции, так как в какой-то момент нужно будет скомпилировать объект, на который ссылается?

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

В C/С++, когда вы компилируете, вы должны помнить, что есть целая цепочка инструментов (препроцессор, компилятор, компоновщик и инструменты управления компоновкой, такие как make или Visual Studio и т.д.)

Ответ 9

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

Как ускорить форвардные декларации компиляции, поскольку в какой-то момент объект, на который нужно ссылаться, должен быть компилируется?

Я могу купить аргумент для сокращения сложности, но что было бы практичным примером этого может быть?

Передовые декларации - плохая задница. Мой опыт в том, что многие программисты на C++ не знают о том, что вам не нужно включать какой-либо заголовочный файл, если вы действительно не хотите использовать какой-либо тип, например. вам нужно определить тип, чтобы компилятор понимал, что вы хотите сделать. Важно попытаться воздержаться от включения файлов заголовков в другие файлы заголовков.

Простое перемещение указателя от одной функции к другой требует только прямого объявления:

// someFile.h
class CSomeClass;
void SomeFunctionUsingSomeClass(CSomeClass* foo);

В том числе someFile.h не требует, чтобы вы включили заголовочный файл CSomeClass, поскольку вы просто передаете ему указатель, не используя класс. Это означает, что компилятору нужно просто проанализировать строку one (класс CSomeClass;) вместо всего файла заголовка (который может быть привязан к другим файлам заголовков и т.д. И т.д.).

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