Почему в C ++ есть заголовочные файлы и файлы .cpp?
c++
header-files
Питер Мортенсен
источник
источник
Ответы:
Ну, основной причиной было бы отделение интерфейса от реализации. Заголовок объявляет, «что» будет делать класс (или что-либо еще реализуемое), в то время как файл cpp определяет, «как» он будет выполнять эти функции.
Это уменьшает зависимости, так что код, который использует заголовок, не обязательно должен знать все детали реализации и любые другие классы / заголовки, необходимые только для этого. Это сократит время компиляции, а также количество необходимой перекомпиляции, когда что-то в реализации изменится.
Это не идеально, и вы обычно прибегаете к методам, таким как Pimpl Idiom, чтобы правильно разделить интерфейс и реализацию, но это хорошее начало.
источник
Компиляция C ++
Компиляция в C ++ выполняется в 2 основных этапа:
Первый - это компиляция «исходных» текстовых файлов в двоичные «объектные» файлы: файл CPP является скомпилированным файлом и компилируется без каких-либо знаний о других файлах CPP (или даже библиотеках), если только он не передан в него через необработанное объявление или заголовок включения. Файл CPP обычно компилируется в .OBJ или .O «объектный» файл.
Второй - это объединение всех «объектных» файлов и, следовательно, создание окончательного двоичного файла (библиотеки или исполняемого файла).
Где ГЭС вписывается во весь этот процесс?
Бедный одинокий файл CPP ...
Компиляция каждого файла CPP не зависит от всех других файлов CPP, что означает, что если A.CPP нужен символ, определенный в B.CPP, например:
Он не скомпилируется, потому что A.CPP не может знать, что doSomethingElse существует ... Если в A.CPP нет объявления, например:
Затем, если у вас есть C.CPP, который использует тот же символ, вы затем копируете / вставляете объявление ...
КОПИЯ / ПАСТА ПРЕДУПРЕЖДЕНИЕ!
Да, есть проблема. Копирование / вставка опасны и сложны в обслуживании. Что означает, что было бы здорово, если бы у нас был какой-то способ НЕ копировать / вставлять, и все же объявить символ ... Как мы можем это сделать? Включением некоторого текстового файла, к которому обычно добавляются суффиксы .h, .hxx, .h ++ или, как я предпочитаю, для файлов C ++ .hpp:
Как
include
работает?Включение файла, по сути, анализирует, а затем копирует и вставляет его содержимое в файл CPP.
Например, в следующем коде с заголовком A.HPP:
... источник B.CPP:
... станет после включения:
Одна маленькая вещь - зачем включать B.HPP в B.CPP?
В текущем случае это не требуется, и B.HPP имеет
doSomethingElse
объявление функции, а B.CPP имеетdoSomethingElse
определение функции (которое само по себе является объявлением). Но в более общем случае, когда B.HPP используется для объявлений (и встроенного кода), не может быть соответствующего определения (например, перечисления, простые структуры и т. Д.), Поэтому включение может быть необходимо, если B.CPP использует эти декларации от B.HPP. В общем, для «хорошего вкуса» источник по умолчанию включает заголовок.Вывод
Таким образом, файл заголовка необходим, потому что компилятор C ++ не может искать только объявления символов, и, таким образом, вы должны помочь ему, включив эти объявления.
И последнее слово: вы должны установить защиту заголовков вокруг содержимого ваших файлов HPP, чтобы быть уверенным, что несколько включений ничего не сломают, но в целом я считаю, что основная причина существования файлов HPP описана выше.
или даже проще
источник
You still have to copy paste the signature from header file to cpp file, don't you?
Нет необходимости. Пока CPP «включает» HPP, прекомпилятор будет автоматически копировать и вставлять содержимое файла HPP в файл CPP. Я обновил ответ, чтобы уточнить это.While compiling A.cpp, compiler knows the types of arguments and return value of doSomethingElse from the call itself
. Нет, это не так. Он знает только типы, предоставленные пользователем, которые половину времени даже не потрудятся прочитать возвращаемое значение. Затем происходят неявные преобразования. И потом, когда у вас есть код:,foo(bar)
вы даже не можете быть уверены, чтоfoo
это функция. Таким образом, компилятор должен иметь доступ к информации в заголовках, чтобы решить, правильно ли компилируется источник, или нет ... Затем, когда код скомпилирован, компоновщик просто свяжет вместе вызовы функций.Seems, they're just a pretty ugly arbitrary design.
: Если C ++ был создан в 2012 году, действительно. Но помните, что C ++ был построен на C в 1980-х годах, и в то время ограничения в то время были совсем другими (IIRC было решено в целях принятия сохранить те же линкеры, что и C).foo(bar)
это функция - если она получена как указатель? На самом деле, говоря о плохом дизайне, я виню C, а не C ++. Мне действительно не нравятся некоторые ограничения чистого C, такие как наличие заголовочных файлов или функции, возвращающие одно и только одно значение, в то же время принимая несколько аргументов на входе (разве не естественно, что ввод и вывод ведут себя одинаково зачем многократные аргументы, а один вывод?) :)Why can't I be sure, that foo(bar) is a function
foo может быть типом, поэтому у вас должен быть вызван конструктор класса.In fact, speaking of bad design, I blame C, not C++
Я могу винить С во многих вещах, но дизайн в 70-х не будет одним из них. Опять же, ограничения того времени ...such as having header files or having functions return one and only one value
: Кортежи могут помочь смягчить это, а также передать аргументы по ссылке. Теперь, каков будет синтаксис для получения возвращенных множественных значений, и стоит ли менять язык?Потому что C, где возникла эта концепция, 30 лет, и тогда это был единственный жизнеспособный способ связать код из нескольких файлов.
Сегодня это ужасный хак, который полностью уничтожает время компиляции в C ++, вызывает бесчисленные ненужные зависимости (потому что определения классов в заголовочном файле предоставляют слишком много информации о реализации) и так далее.
источник
Поскольку в C ++ конечный исполняемый код не несет никакой символьной информации, это более или менее чистый машинный код.
Таким образом, вам нужен способ описания интерфейса фрагмента кода, который отделен от самого кода. Это описание находится в заголовочном файле.
источник
Потому что C ++ унаследовал их от C. К сожалению.
источник
Потому что люди, которые проектировали формат библиотеки, не хотели «тратить» пространство на редко используемую информацию, такую как макросы препроцессора C и объявления функций.
Поскольку вам нужна эта информация, чтобы сообщить компилятору «эта функция доступна позже, когда компоновщик выполняет свою работу», они должны были найти второй файл, в котором эта общая информация могла бы храниться.
Большинство языков после C / C ++ хранят эту информацию в выводе (например, байт-код Java) или вообще не используют предварительно скомпилированный формат, всегда распределяются в исходной форме и компилируются на лету (Python, Perl).
источник
Это препроцессорный способ объявления интерфейсов. Вы помещаете интерфейс (объявления методов) в заголовочный файл, а реализацию в cpp. Приложения, использующие вашу библиотеку, должны знать только интерфейс, к которому они могут получить доступ через #include.
источник
Часто вам захочется получить определение интерфейса без необходимости доставки всего кода. Например, если у вас есть общая библиотека, вы бы отправили вместе с ней файл заголовка, который определяет все функции и символы, используемые в общей библиотеке. Без заголовочных файлов вам понадобится отправить исходный код.
Внутри одного проекта заголовочные файлы используются, IMHO, как минимум для двух целей:
источник
Отвечая на ответ MadKeithV ,
Другая причина в том, что заголовок дает уникальный идентификатор каждому классу.
Так что, если у нас есть что-то вроде
У нас будут ошибки, когда мы попытаемся построить проект, так как A является частью B, с заголовками мы бы избежали такого рода головной боли ...
источник