Почему С++ нужен отдельный заголовочный файл?

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

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


Следующий вопрос:

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

Ответ 1

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

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

Причины, по которым вы хотите разделить, следующие:

  • Чтобы улучшить время сборки.
  • Ссылка на код без источника для определений.
  • Чтобы не маркировать все "встроенные".

Если ваш более общий вопрос: "Почему С++ не идентичен Java?", тогда я должен спросить: "Почему вы пишете С++ вместо Java?";-p

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

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

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

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

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

Ответ 4

К моему (ограниченному - я вообще не разработчик 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. Затем вам нужно включить # SomeOther.c в Some.c. Теперь добавьте некоторые функции "private" в SomeOther.c. Поскольку C не знает частных функций, эта функция будет доступна и в Some.c.

Здесь находятся файлы .h. Они определяют все функции (и переменные), которые вы хотите "Экспортировать" из файла .c, к которому можно получить доступ в других файлах .c. Таким образом, вы получаете что-то вроде области Public/Private. Кроме того, вы можете предоставить этот .h файл другим людям без совместного использования исходного кода. Файлы .h также работают с скомпилированными .lib файлами.

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

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

Ответ 5

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

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

Ответ 6

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

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

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

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

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

Ответ 7

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

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

Ответ 8

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

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

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

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

Ответ 9

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

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

Ответ 10

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

Ответ 11

Хорошо, вы можете отлично разрабатывать С++ без файлов заголовков. На самом деле некоторые библиотеки, которые интенсивно используют шаблоны, не используют парадигму заголовков/кодовых файлов (см. Boost). Но в C/С++ вы не можете использовать то, что не объявлено. Один практический способ дело в том, чтобы использовать заголовочные файлы. Кроме того, вы получаете преимущество совместного использования интерфейса без совместного использования кода/реализации. И я думаю, что это не было задумано создателями C: когда вы используете общие файлы заголовков, вы должны использовать знаменитый:

#ifndef MY_HEADER_SWEET_GUARDIAN
#define MY_HEADER_SWEET_GUARDIAN

// [...]
// my header
// [...]

#endif // MY_HEADER_SWEET_GUARDIAN

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

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

Еще одно бремя для нас, бедных пользователей С++...

Ответ 12

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

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

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

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

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

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

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

Ответ 13

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