Почему нам нужно включать .h
и .cpp
файлы, и файлы, в то время как мы можем заставить работать только .cpp
файлы?
Например: создание file.h
содержащих объявлений, затем создание file.cpp
содержащих определений и включение обоих в main.cpp
.
Альтернативно: создание file.cpp
содержащей декларации / определений (без прототипов), включая их в main.cpp
.
Оба работают на меня. Я не вижу разницы. Может быть, поможет понимание процесса компиляции и компоновки.
Ответы:
Хотя вы можете включать
.cpp
файлы, как вы упомянули, это плохая идея.Как вы упомянули, объявления принадлежат заголовочным файлам. Это не вызывает проблем при включении в несколько модулей компиляции, поскольку они не включают реализации. Включение определения функции или члена класса несколько раз обычно вызывает проблему (но не всегда), потому что компоновщик запутается и выдаст ошибку.
Что должно произойти, так это то, что каждый
.cpp
файл содержит определения для подмножества программы, такого как класс, логически организованная группа функций, глобальные статические переменные (используйте экономно, если вообще используется) и т. Д.Каждый модуль (
.cpp
файл) компиляции затем включает в себя любые объявления, необходимые для компиляции содержащихся в нем определений. Он отслеживает функции и классы, на которые он ссылается, но не содержит их, поэтому компоновщик может разрешить их позже, когда объединит объектный код в исполняемый файл или библиотеку.пример
Foo.h
-> содержит объявление (интерфейс) для класса Foo.Foo.cpp
-> содержит определение (реализацию) для класса Foo.Main.cpp
-> содержит основной метод, точку входа в программу. Этот код создает экземпляр Foo и использует его.И то,
Foo.cpp
и другоеMain.cpp
нужно включитьFoo.h
.Foo.cpp
он нужен, потому что он определяет код, который поддерживает интерфейс класса, поэтому ему нужно знать, что это за интерфейс.Main.cpp
он нужен, потому что он создает Foo и вызывает его поведение, поэтому он должен знать, каково это поведение, размер Foo в памяти, как найти его функции и т. д., но пока ему пока не нужна фактическая реализация.Компилятор сгенерирует
Foo.o
изFoo.cpp
которого содержится весь код класса Foo в скомпилированной форме. Он также генерирует,Main.o
который включает основной метод и неразрешенные ссылки на класс Foo.Сейчас идет компоновщик, который сочетает в себе два объектных файлов
Foo.o
иMain.o
в исполняемый файл. Он видит неразрешенные ссылки Foo,Main.o
но видит, чтоFoo.o
содержит необходимые символы, поэтому он «соединяет точки», так сказать. Вызов функцииMain.o
теперь связан с фактическим местоположением скомпилированного кода, поэтому во время выполнения программа может перейти в правильное местоположение.Если бы вы включили
Foo.cpp
файл вMain.cpp
, было бы два определения класса Foo. Компоновщик увидит это и скажет: «Я не знаю, какой из них выбрать, так что это ошибка». Этап компиляции будет успешным, но соединение не будет. (Если вы просто не компилируете,Foo.cpp
но тогда почему это в отдельном.cpp
файле?)Наконец, идея различных типов файлов не имеет отношения к компилятору C / C ++. Он компилирует «текстовые файлы», которые, как мы надеемся, содержат действительный код для желаемого языка. Иногда это может быть в состоянии сказать язык на основе расширения файла. Например, скомпилируйте
.c
файл без опций компилятора, и он примет C, а расширение.cc
или.cpp
скажет, что он принимает C ++. Тем не менее, я могу легко сказать компилятору скомпилировать файл.h
или даже.docx
файл как C ++, и он будет генерировать.o
файл object ( ), если он содержит допустимый код C ++ в текстовом формате. Эти расширения больше для пользы программиста. Если я вижуFoo.h
иFoo.cpp
, я сразу предполагаю, что первый содержит объявление класса, а второй содержит определение.источник
Foo.cpp
в него,Main.cpp
вам не нужно включать.h
файл, у вас есть на один файл меньше, вы все равно выиграете, разделив код на отдельные файлы для удобства чтения, и ваша команда компиляции будет проще. Хотя я понимаю важность заголовочных файлов, я не думаю, что это такIf you had included the Foo.cpp file in Main.cpp, there would be two definitions of class Foo.
Самое важное предложение относительно вопроса.Узнайте больше о роли препроцессора C и C ++ , который концептуально является первой «фазой» компилятора C или C ++ (исторически это была отдельная программа
/lib/cpp
; теперь, по соображениям производительности, она интегрирована внутри самого компилятораcc1
илиcc1plus
). Прочитайте, в частности, документациюcpp
препроцессора GNU . Таким образом, на практике компилятор концептуально сначала обрабатывает ваш модуль компиляции (или модуль перевода ), а затем работает с предварительно обработанной формой.Вам, вероятно, нужно будет всегда включать заголовочный файл,
file.h
если он содержит (в соответствии с соглашениями и привычками ):typedef
,struct
иclass
т. д., ...)static inline
функцийОбратите внимание, что условием (и удобством) является их размещение в заголовочном файле.
Конечно, для вашей реализации
file.cpp
нужно все вышеперечисленное, поэтому хочется#include "file.h"
сначала.Это соглашение (но очень распространенное). Вы можете избежать заголовочных файлов и копировать и вставлять их содержимое в файлы реализации (то есть единицы перевода). Но вы этого не хотите (за исключением, может быть, если ваш код на C или C ++ генерируется автоматически; тогда вы можете заставить программу-генератор выполнять это копирование и вставку, имитируя роль препроцессора).
Дело в том, что препроцессор выполняет только текстовые операции. Вы можете (в принципе) полностью избежать этого путем копирования и вставки или заменить его другим «препроцессором» или генератором кода C (например, gpp или m4 ).
Дополнительной проблемой является то, что последние стандарты C (или C ++) определяют несколько стандартных заголовков. Большинство реализаций действительно реализуют эти стандартные заголовки в виде (специфичных для реализации) файлов , но я считаю, что для соответствующей реализации было бы возможно реализовать стандартные включения (например,
#include <stdio.h>
для C или#include <vector>
для C ++) с некоторыми магическими приемами (например, с использованием некоторой базы данных или некоторых информация внутри компилятора).При использовании компиляторов GCC (например,
gcc
илиg++
) вы можете использовать-H
флаг, чтобы получать информацию о каждом включении, и-C -E
флаги, чтобы получить предварительно обработанную форму. Конечно, есть много других флагов компилятора, влияющих на предварительную обработку (например,-I /some/dir/
для добавления/some/dir/
для поиска во включаемых файлах и-D
для предопределения некоторого макроса препроцессора и т. Д., И т. Д. ....).NB. Будущие версии C ++ (возможно, C ++ 20 , возможно, даже позже) могут иметь модули C ++ .
источник
Из-за многокомпонентной модели сборки C ++ вам нужен способ иметь код, который появляется в вашей программе только один раз (определения), и вам нужен способ иметь код, который появляется в каждой единице перевода вашей программы (объявления).
Отсюда идиома заголовка C ++. Это соглашение по причине.
Вы можете выгрузить всю свою программу в один модуль перевода, но это создает проблемы с повторным использованием кода, модульным тестированием и обработкой зависимостей между модулями. Это также просто большой беспорядок.
источник
Выбранный ответ из Почему мы должны написать файл заголовка? это разумное объяснение, но я хотел бы добавить дополнительные детали.
Кажется, что рациональное использование заголовочных файлов имеет тенденцию теряться при обучении и обсуждении C / C ++.
Файл заголовков обеспечивает решение двух проблем разработки приложений:
C / C ++ может масштабироваться от небольших программ до очень больших многомиллионных, многотысячных файловых программ. Разработка приложений может масштабироваться от команд одного разработчика до сотен разработчиков.
Вы можете носить несколько шляп в качестве разработчика. В частности, вы можете быть пользователем интерфейса для функций и классов, или вы можете быть автором интерфейса функций и классов.
Когда вы используете функцию, вам нужно знать интерфейс функции, какие параметры использовать, что возвращает функция, и вам нужно знать, что делает функция. Это легко документируется в заголовочном файле, даже не глядя на реализацию. Когда вы прочитали реализацию
printf
? Купить мы используем это каждый день.Когда вы являетесь разработчиком интерфейса, шляпа меняет другое направление. Заголовочный файл обеспечивает объявление открытого интерфейса. Заголовочный файл определяет, что нужно другой реализации для использования этого интерфейса. Информация, внутренняя и закрытая для этого нового интерфейса, не должна (и не должна) объявляться в заголовочном файле. Публичный заголовочный файл должен быть всем, кто должен использовать модуль.
Для крупномасштабной разработки компиляция и компоновка могут занять много времени. От многих минут до многих часов (даже до многих дней!). Разделение программного обеспечения на интерфейсы (заголовки) и реализации (источники) предоставляет метод для компиляции только тех файлов, которые необходимо скомпилировать, а не перестраивать все.
Кроме того, файлы заголовков позволяют разработчику предоставить библиотеку (уже скомпилированную), а также файл заголовков. Другие пользователи библиотеки могут даже не видеть правильную реализацию, но все же могут использовать библиотеку с файлом заголовка. Вы делаете это каждый день с помощью стандартной библиотеки C / C ++.
Даже если вы разрабатываете небольшое приложение, использование крупномасштабных методов разработки программного обеспечения является хорошей привычкой. Но мы также должны помнить, почему мы используем эти привычки.
источник
Вы также можете спросить, почему бы просто не поместить весь код в один файл.
Самый простой ответ - обслуживание кода.
Есть моменты, когда разумно создать класс:
Время, когда разумно полностью встроить заголовок, - это когда класс на самом деле является структурой данных с несколькими базовыми получателями и установщиками и, возможно, конструктором, который принимает значения для инициализации своих членов.
(Шаблоны, которые должны быть встроены, это немного другая проблема).
В другой раз, когда вы создаете класс в заголовке, вы можете использовать этот класс в нескольких проектах и, в частности, захотите избежать ссылок в библиотеках.
Время, когда вы можете включить целый класс в модуль компиляции и вообще не показывать его заголовок:
Класс «impl», который используется только тем классом, который он реализует. Это деталь реализации этого класса, и внешне она не используется.
Реализация абстрактного базового класса, который создается каким-то фабричным методом, который возвращает указатель / ссылку / умный указатель) в базовый класс. Заводской метод был бы выставлен самим классом, не было бы. (Кроме того, если у класса есть экземпляр, который регистрируется в таблице через статический экземпляр, его даже не нужно показывать через фабрику).
Класс типа «функтор».
Другими словами, если вы не хотите, чтобы кто-либо включал заголовок.
Я знаю, о чем вы могли подумать ... Если это просто для удобства обслуживания, включив файл cpp (или полностью встроенный заголовок), вы можете легко отредактировать файл, чтобы «найти» код и просто перестроить.
Однако «ремонтопригодность» заключается не только в том, что код выглядит аккуратно. Это вопрос воздействия изменений. Общеизвестно, что если вы оставите заголовок без изменений и просто измените
(.cpp)
файл реализации , вам не потребуется перестраивать другой источник, потому что не должно быть никаких побочных эффектов.Это делает «более безопасным» внесение таких изменений, не беспокоясь об эффекте зацепки, и именно это на самом деле означает «ремонтопригодность».
источник
Пример Snowman нуждается в небольшом расширении, чтобы точно показать, зачем нужны файлы .h.
Добавьте в игру еще один класс Bar, который также зависит от класса Foo.
Foo.h -> содержит объявление для класса Foo
Foo.cpp -> содержит определение (имплементацию) класса Foo
Main.cpp -> использует переменную типа Foo.
Bar.cpp -> тоже использует переменную типа Foo.
Теперь все файлы cpp должны включать Foo.h. Было бы ошибкой включать Foo.cpp в более чем один другой файл cpp. Компоновщик потерпит неудачу, потому что класс Foo будет определен более одного раза.
источник
.h
файлах, в любом случае - с включенной защитой, и это точно та же проблема, что и у вас с.h
файлами. Это не то, что.h
файлы вообще..cpp
файл.Если вы напишите весь код в одном и том же файле, это сделает ваш код безобразным.
Во-вторых, вы не сможете поделиться своим письменным классом с другими. В соответствии с разработкой программного обеспечения вы должны написать код клиента отдельно. Клиент не должен знать, как работает ваша программа. Они просто нуждаются в выводе. Если вы напишите всю программу в одном файле, это приведет к потере безопасности вашей программы.
источник