Зачем C ++ нужен отдельный заголовочный файл? - PullRequest
113 голосов
/ 20 августа 2009

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

C ++ был ратифицирован в 1998 году, так почему же он разработан таким образом? Какие преимущества имеет наличие отдельного заголовочного файла?


Контрольный вопрос:

Как компилятор находит файл .cpp с кодом в нем, когда я включаю только файл .h? Предполагается ли, что файл .cpp имеет то же имя, что и файл .h, или он просматривает все файлы в дереве каталогов?

Ответы [ 13 ]

96 голосов
/ 20 августа 2009

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

Ответ в том, что C ++ не «нуждается» в этом. Если вы пометите все как встроенное (что в любом случае является автоматическим для функций-членов, определенных в определении класса), разделение не требуется. Вы можете просто определить все в заголовочных файлах.

Причины, по которым вы можете хотеть отделиться:

  1. Чтобы улучшить время сборки.
  2. Для ссылки на код без источника определений.
  3. Чтобы не пометить все как «встроенные».

Если ваш более общий вопрос «почему C ++ не идентичен Java?», То я должен спросить: «Почему вы пишете C ++ вместо Java?» ; Р

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

Итак, #include - это прямая текстовая замена. Если вы определяете все в заголовочных файлах, препроцессор в итоге создает огромную копию и вставку каждого исходного файла в вашем проекте и передает его в компилятор. Тот факт, что стандарт C ++ был ратифицирован в 1998 году, не имеет к этому никакого отношения, это тот факт, что среда компиляции для C ++ так тесно основана на среде C.

Преобразование моих комментариев для ответа на следующий вопрос:

Как компилятор находит файл .cpp с кодом в нем

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

80 голосов
/ 20 августа 2009

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

Новые скомпилированные языки (такие как Java, C #) не использовать вперед декларации; идентификаторы распознается автоматически из источника файлы и читать напрямую из динамического библиотека символов. Это означает, что заголовок файлы не нужны.

53 голосов
/ 20 августа 2009

Некоторые люди считают заголовочные файлы преимуществом:

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

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

И C ++ сохранил эту систему для обратной совместимости.

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

Однако одно из предложений для C ++ 0x состояло в том, чтобы добавить правильную систему модулей, позволяющую компилировать код, подобный .NET или Java, в более крупные модули, причем все сразу, без заголовков. Это предложение не привело к сокращению C ++ 0x, но я считаю, что оно все еще находится в категории «мы хотели бы сделать это позже». Возможно в TR2 или аналогичном.

26 голосов
/ 20 августа 2009

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

Например, следующее должно выдавать ошибку компилятора:

void SomeFunction() {
    SomeOtherFunction();
}

void SomeOtherFunction() {
    printf("What?");
}

Ошибка должна заключаться в том, что «SomeOtherFunction не объявлена», потому что вы вызываете ее до ее объявления. Один из способов исправить это - переместить SomeOtherFunction над SomeFunction. Другой подход - сначала объявить сигнатуру функций:

void SomeOtherFunction();

void SomeFunction() {
    SomeOtherFunction();
}

void SomeOtherFunction() {
    printf("What?");
}

Это позволяет компилятору узнать: где-то в коде есть функция SomeOtherFunction, которая возвращает void и не принимает никаких параметров. Поэтому, если вам нужен код, который пытается вызвать SomeOtherFunction, не паникуйте, а вместо этого отправляйтесь на его поиск.

Теперь представьте, что у вас есть SomeFunction и SomeOtherFunction в двух разных файлах .c. Затем вы должны #include "SomeOther.c" в Some.c. Теперь добавьте некоторые «частные» функции в SomeOther.c. Поскольку C не знает приватных функций, эта функция будет доступна и в Some.c.

Вот где приходят файлы .h: они определяют все функции (и переменные), которые вы хотите «экспортировать» из файла .c, к которому можно получить доступ в других файлах .c. Таким образом, вы получаете что-то вроде публичного / частного объема. Кроме того, вы можете передать этот файл .h другим людям без необходимости делиться своим исходным кодом - файлы .h также работают с скомпилированными файлами .lib.

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

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

10 голосов
/ 20 августа 2009

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

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

5 голосов
/ 20 августа 2009

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

Чтобы компенсировать это ограничение, C и C ++ допускают объявления, и эти объявления могут быть включены в модули, которые используют их с помощью директивы препроцессора #include.

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

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

4 голосов
/ 20 августа 2009

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

Да, на данный момент (через 10 лет после первого стандарта C ++ и через 20 лет после того, как он начал серьезно расти в использовании), легко спросить, почему у него нет надлежащей системы модулей. Очевидно, что любой новый язык, разрабатываемый сегодня, не будет работать как C ++. Но дело не в C ++.

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

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

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

3 голосов
/ 20 августа 2009

Что ж, C ++ был ратифицирован в 1998 году, но он использовался гораздо дольше, и ратификация в основном устанавливала текущее использование, а не навязывала структуру. И поскольку C ++ был основан на C, а C имеет заголовочные файлы, в C ++ они тоже есть.

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

Скажем, у меня есть foo.cpp, и я хочу использовать код из файлов bar.h / bar.cpp.

Я могу #include "bar.h" в foo.cpp, а затем запрограммировать и скомпилировать foo.cpp, даже если bar.cpp не существует. Заголовочный файл действует как обещание компилятору, что классы / функции в bar.h будут существовать во время выполнения, и в нем есть все, что ему нужно уже знать.

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

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

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

3 голосов
/ 20 августа 2009

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

Это действительно проблема масштаба.

1 голос
/ 20 августа 2009

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

...