Действительно ли заголовочные файлы хороши? [закрыто]

20

Я считаю, что заголовочные файлы полезны при просмотре исходных файлов C ++, потому что они дают «сводку» всех функций и членов данных в классе. Почему многие другие языки (такие как Ruby, Python, Java и т. Д.) Не имеют такой функции? Является ли это областью, где многословие C ++ пригодится?

Мэтт Фичман
источник
Я не знаком с инструментами в C и C ++, но большинство IDE / редакторов кода, в которых я работал, имеют представление «структура» или «структура».
Майкл К
1
Заголовочные файлы не хороши - они необходимы! - Смотрите также этот ответ на SO: stackoverflow.com/a/1319570/158483
Пит

Ответы:

31

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

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

Для модульности заголовочные файлы были необходимы в качестве метаданных о коде в модулях. Например. какие методы (и в классах C ++) доступны в какой библиотеке. Было очевидно, что разработчик написал это, потому что время компиляции было дорогим. В настоящее время нет проблем с тем, чтобы компилятор генерировал эти метаданные из самого кода. Языки Java и .NET делают это нормально.

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

Euphoric
источник
8
Я согласен с большей частью того, что вы пишете, но последний абзац делает вещи немного простыми. Заголовочные файлы все еще служат полезной цели для определения общедоступных интерфейсов.
Бенджамин Баннье
3
@honk Это то, что я понял с моим вопросом. Но я думаю, что современные IDE (как предложил ElYusubov) отрицают это.
Мэтт Фичман
5
Вы можете добавить, что комитет C ++ работает над модулями, которые позволили бы C и C ++ работать без заголовков ИЛИ с использованием заголовков, используемых более эффективно. Это сделало бы этот ответ более справедливым.
Klaim
2
@MattFichman: Генерация некоторого заголовочного файла - это одна вещь (которую редакторы / IDE большинства программистов могут каким-то образом делать автоматически), но суть открытого API заключается в том, что его нужно эффективно определять и разрабатывать настоящим человеком.
Бенджамин Баннье
2
@honk Да. Но нет причин для этого быть определенным в отдельных файлах. Может быть в том же коде, что и реализация. Так же, как это делает C #.
Эйфорическая
17

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

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

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

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

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

В любом случае вы можете «просматривать» данные, созданные на этом шаге, используя языковые инструменты. Обычно в среде IDE используется браузер классов. И если у языка есть REPL, его также часто можно использовать для генерации сводной документации по любым загруженным объектам.

tylerl
источник
5
не совсем верно - большинство, если не все, компиляторы предварительно компилируют заголовки так, как они их видят, поэтому включение их в следующий модуль компиляции не хуже, чем поиск блока предварительно скомпилированного кода. Он ничем не отличается от, скажем, кода .NET, постоянно «собирающего» system.data.types для каждого файла C #, который вы компилируете. Причина, по которой программы на C / C ++ занимают так много времени, заключается в том, что они фактически скомпилированы (и оптимизированы). Все, что нужно .NET-программе - это анализировать ее в IL, среда выполнения выполняет фактическую компиляцию через JIT. Тем не менее, 300 исходных файлов в коде C # тоже требуют много времени для компиляции.
gbjbaanb
7

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

Например: Visual Studio имеет, Class Viewи Object browserэто приятно обеспечивает запрошенную функциональность. Как на следующих скриншотах.

введите описание изображения здесь

введите описание изображения здесь

Юсубы
источник
4

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

Это в сочетании с макросистемой C (унаследованной от C ++) приводит ко многим подводным камням и запутанным ситуациям.

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

jcora
источник
2

Мне всегда нравились заголовочные файлы, так как они предоставляют форму интерфейса для реализации вместе с некоторой дополнительной информацией, такой как переменные класса, все в одном простом для просмотра файле.

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

Так что C / C ++ предписывает эквивалент (своего рода) интерфейса в заголовочных файлах - это хорошо.

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

gbjbaanb
источник
Интерфейс и реализация в разных файлах? Если вам нужно это по своему замыслу, все еще очень часто определяют интерфейс (или абстрактный класс) в таких языках, как Java / C #, в одном файле и скрывают реализацию (и) в других файлах. Это не требует старых и хитрых заголовочных файлов.
Петтер Нордландер
а? не то, что я сказал - вы определяете интерфейс и реализацию класса в 2 файлах.
gbjbaanb
2
ну, это не требует заголовочных файлов и это не делает заголовочные файлы лучше. Да, они нужны в устаревших языках, но в качестве концепции они не нужны для разделения интерфейса / реализации в разных файлах (фактически, они делают это хуже).
Петтер Нордландер
@Petter Я думаю, что вы неправильно поняли, концептуально они ничем не отличаются - существует заголовок для обеспечения интерфейса на языке C, и хотя вы правы - вы можете объединить 2 (как многие делают с C-кодом только для заголовков), это не лучшие практики, которые я видел, следовали во многих местах. Например, все разработчики C # отделили интерфейс от класса. Это хорошая практика, так как тогда вы можете включить файл интерфейса в несколько других файлов без загрязнения. Теперь, если вы думаете, что это разделение является наилучшей практикой (то есть), то путь к достижению этого в C - это использование заголовочных файлов.
gbjbaanb
Если заголовки действительно полезны для разделения интерфейса и реализации, то такие вещи, как PImpl, не понадобятся, чтобы скрыть частные компоненты и отделить конечных пользователей от необходимости перекомпилировать их. Вместо этого мы получаем худшее из обоих миров. Заголовки симулируют быстро исчезающий фасад разделения, открывая множество ловушек, которые требуют подробного шаблонного кода, если вам нужно обойти их.
underscore_d
1

Я бы на самом деле сказал, что файлы заголовков не очень хороши, потому что они запутывают интерфейс и реализацию. Цель программирования в целом, и особенно ООП, состоит в том, чтобы иметь определенный интерфейс и скрывать детали реализации, но в заголовочном файле C ++ показаны как методы, наследование и открытые члены (интерфейс), так и частные методы и частные члены. (некоторая часть реализации). Не говоря уже о том, что в некоторых случаях вы в конечном итоге встраиваете код или конструкторы в заголовочный файл, а некоторые библиотеки включают шаблонный код в заголовки, который действительно смешивает реализацию с интерфейсом.

Я полагаю, что первоначальная цель состояла в том, чтобы позволить коду использовать другие библиотеки, объекты и т. Д. Без импорта всего содержимого скрипта. Все, что вам нужно, это заголовок для компиляции и ссылки. Это экономит время и циклы таким образом. В этом случае это хорошая идея, но это только один из способов решения этих проблем.

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

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

Лучший ответ - придерживаться того, что нужно делать, и делать вас счастливыми.

Морис Ривз
источник
0

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

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

Supercat
источник