Насколько мне известно, стандарт C ++ не знает разницы между файлами заголовков и исходными файлами. Что касается языка, любой текстовый файл с юридическим кодом такой же, как и любой другой. Однако, хотя это и не является незаконным, включение исходных файлов в вашу программу в значительной степени устранит любые преимущества, которые вы могли бы получить от разделения исходных файлов в первую очередь.
По сути, #include
сообщает препроцессор , чтобы взять весь указанный вами файл и скопировать его в ваш активный файл, прежде чем компилятор получит его в свои руки. Итак, когда вы включаете все исходные файлы в свой проект вместе, принципиально нет разницы между тем, что вы сделали, и просто созданием одного огромного исходного файла без какого-либо разделения.
«О, в этом нет ничего страшного. Если он пойдет, все в порядке», Я слышу, как вы плачете. И в каком-то смысле вы правы. Но прямо сейчас вы имеете дело с крохотной маленькой программой и красивым и относительно свободным процессором, который скомпилирует ее за вас. Вам не всегда так везет.
Если вы когда-нибудь окунетесь в сферу серьезного компьютерного программирования, вы увидите проекты, в которых количество строк может достигать миллионов, а не десятков. Это много строк. И если вы попытаетесь скомпилировать один из них на современном настольном компьютере, это может занять несколько часов вместо секунд.
«О нет! Звучит ужасно! Но могу ли я предотвратить эту ужасную судьбу ?!» К сожалению, с этим мало что можно сделать. Если на компиляцию уходит часы, на компиляцию уходит часы. Но это действительно имеет значение только в первый раз - после того, как вы скомпилировали его один раз, нет причин для его повторной компиляции.
Если вы что-то не измените.
Теперь, если бы у вас было два миллиона строк кода, объединенные вместе в один гигантский бегемот, и вам нужно сделать простое исправление ошибки, например, x = y + 1
, что означает, что вам нужно снова скомпилировать все два миллиона строк, чтобы проверить это. И если вы обнаружите, что хотели вместо этого выполнить x = y - 1
, то снова вас ждут два миллиона строк компиляции. Это много часов потраченного впустую времени, которые можно было бы потратить на что-нибудь еще.
«Но я ненавижу непродуктивность! Если бы только был какой-то способ скомпилировать отдельные части моей кодовой базы по отдельности и каким-то образом ] свяжите их вместе потом! " Отличная идея, в теории. Но что, если вашей программе нужно знать, что происходит в другом файле? Невозможно полностью разделить вашу кодовую базу, если вы не хотите вместо этого запускать кучу крошечных крошечных файлов .exe.
«Но, конечно, это должно быть возможно! В противном случае программирование звучит как чистая пытка! Что, если бы я нашел способ разделить из реализации ? Скажем, взять достаточно информации из этих отдельных сегментов кода, чтобы идентифицировать их для остальной части программы, и поместить их вместо этого в какой-то файл заголовка ? И таким образом, Я могу использовать директиву препроцессора #include
, чтобы ввести только ту информацию, которая необходима для компиляции! »
Хм. Возможно, вы там что-то заметили. Сообщите мне, как это работает для вас.
теоретически. Но что, если вашей программе нужно знать, что происходит в другом файле? Невозможно полностью разделить вашу кодовую базу, если вы не хотите вместо этого запускать кучу крошечных крошечных файлов .exe. «Но, конечно, это должно быть возможно! В противном случае программирование звучит как чистая пытка! Что, если бы я нашел способ разделить из реализации ? Скажем, взять достаточно информации из этих отдельных сегментов кода, чтобы идентифицировать их для остальной части программы, и поместить их вместо этого в какой-то файл заголовка ? И таким образом, Я могу использовать директиву препроцессора #include
, чтобы ввести только ту информацию, которая необходима для компиляции! »
Хм. Возможно, вы там что-то заметили. Сообщите мне, как это работает для вас.
теоретически. Но что, если вашей программе нужно знать, что происходит в другом файле? Невозможно полностью разделить вашу кодовую базу, если вы не хотите вместо этого запускать кучу крошечных крошечных файлов .exe. «Но, конечно, это должно быть возможно! В противном случае программирование звучит как чистая пытка! Что, если бы я нашел способ разделить из реализации ? Скажем, взять достаточно информации из этих отдельных сегментов кода, чтобы идентифицировать их для остальной части программы, и поместить их вместо этого в какой-то файл заголовка ? И таким образом, Я могу использовать директиву препроцессора #include
, чтобы ввести только ту информацию, которая необходима для компиляции! »
Хм. Возможно, вы там что-то заметили. Сообщите мне, как это работает для вас.
Но что, если вашей программе нужно знать, что происходит в другом файле? Невозможно полностью разделить вашу кодовую базу, если вы не хотите вместо этого запускать кучу крошечных крошечных файлов .exe. «Но, конечно, это должно быть возможно! В противном случае программирование звучит как чистая пытка! Что, если бы я нашел способ разделить из реализации ? Скажем, взять достаточно информации из этих отдельных сегментов кода, чтобы идентифицировать их для остальной части программы, и поместить их вместо этого в какой-то файл заголовка ? И таким образом, Я могу использовать директиву препроцессора #include
, чтобы ввести только ту информацию, которая необходима для компиляции! »
Хм. Возможно, вы там что-то заметили. Сообщите мне, как это работает для вас.
Но что, если вашей программе нужно знать, что происходит в другом файле? Невозможно полностью разделить вашу кодовую базу, если вы не хотите вместо этого запускать кучу крошечных крошечных файлов .exe. «Но, конечно, это должно быть возможно! В противном случае программирование звучит как чистая пытка! Что, если бы я нашел способ разделить из реализации ? Скажем, взять достаточно информации из этих отдельных сегментов кода, чтобы идентифицировать их для остальной части программы, и поместить их вместо этого в какой-то файл заголовка ? И таким образом, Я могу использовать директиву препроцессора #include
, чтобы ввести только ту информацию, которая необходима для компиляции! »
Хм. Возможно, вы там что-то заметили. Сообщите мне, как это работает для вас.
невозможно полностью разделить вашу кодовую базу, если вы не хотите вместо этого запускать кучу крошечных крошечных файлов .exe. «Но, конечно, это должно быть возможно! В противном случае программирование звучит как чистая пытка! Что, если бы я нашел способ разделить из реализации ? Скажем, взять достаточно информации из этих отдельных сегментов кода, чтобы идентифицировать их для остальной части программы, и поместить их вместо этого в какой-то файл заголовка ? И таким образом, Я могу использовать директиву препроцессора #include
, чтобы ввести только ту информацию, которая необходима для компиляции! »
Хм. Возможно, вы там что-то заметили. Сообщите мне, как это работает для вас.
невозможно полностью разделить вашу кодовую базу, если вы не хотите вместо этого запускать кучу крошечных крошечных файлов .exe. «Но, конечно, это должно быть возможно! В противном случае программирование звучит как чистая пытка! Что, если бы я нашел способ разделить из реализации ? Скажем, взять достаточно информации из этих отдельных сегментов кода, чтобы идентифицировать их для остальной части программы, и поместить их вместо этого в какой-то файл заголовка ? И таким образом, Я могу использовать директиву препроцессора #include
, чтобы ввести только ту информацию, которая необходима для компиляции! »
Хм. Возможно, вы там что-то заметили. Сообщите мне, как это работает для вас.
Но ведь это должно быть возможно! В остальном программирование звучит как чистая пытка! Что, если бы я нашел способ отделить интерфейс от реализации ? Скажем, взять достаточно информации из этих отдельных сегментов кода, чтобы идентифицировать их для остальной части программы, и вместо этого поместить их в какой-то файл заголовка ? И таким образом я могу использовать директиву препроцессора #include
, чтобы вводить только ту информацию, которая необходима для компиляции! »
Хм. Возможно, вы что-то там знаете. Дайте мне знать, как это работает для вас.
Но ведь это должно быть возможно! В остальном программирование звучит как чистая пытка! Что, если бы я нашел способ отделить интерфейс от реализации ? Скажем, взять достаточно информации из этих отдельных сегментов кода, чтобы идентифицировать их для остальной части программы, и поместить их вместо этого в какой-то файл заголовка ? И таким образом я могу использовать директиву препроцессора #include
, чтобы вводить только ту информацию, которая необходима для компиляции! »
Хм. Возможно, вы что-то там знаете. Дайте мне знать, как это работает для вас.
и поместить их в какой-то файл заголовка ? И таким образом я могу использовать директиву препроцессора #include
, чтобы вводить только ту информацию, которая необходима для компиляции! »
Хм. Возможно, вы что-то там знаете. Дайте мне знать, как это работает для вас.
и поместить их в какой-то файл заголовка ? И таким образом я могу использовать директиву препроцессора #include
, чтобы вводить только ту информацию, которая необходима для компиляции! »
Хм. Возможно, вы что-то там знаете. Сообщите мне, как это работает для вас.
Вероятно, это более подробный ответ, чем вы хотели, но я думаю, что достойное объяснение оправдано.
В C и C ++ один исходный файл определяется как одна единица перевода . По соглашению файлы заголовков содержат объявления функций, определения типов и определения классов. Фактические реализации функций находятся в единицах перевода, то есть в файлах .cpp.
Идея заключается в том, что функции и функции-члены класса / структуры компилируются и собираются один раз, тогда другие функции могут вызывать этот код из одного места, не создавая дубликатов. Ваши функции неявно объявляются как «extern».
/* Function declaration, usually found in headers. */
/* Implicitly 'extern', i.e the symbol is visible everywhere, not just locally.*/
int add(int, int);
/* function body, or function definition. */
int add(int a, int b)
{
return a + b;
}
Если вы хотите, чтобы функция была локальной для единицы перевода, вы определяете ее как «статическую». Что это значит? Это означает, что если вы включите исходные файлы с внешними функциями, вы получите ошибки переопределения, потому что компилятор столкнется с одной и той же реализацией более одного раза. Итак, вы хотите, чтобы все ваши единицы трансляции видели объявление функции , но не тело функции .
Так как же все это получается в конце? Это работа линкера. Компоновщик читает все объектные файлы, созданные на этапе ассемблера, и разрешает символы. Как я уже сказал ранее, символ - это просто имя. Например, имя переменной или функции. Когда единицы трансляции, которые вызывают функции или объявляют типы, не знают реализации этих функций или типов, эти символы называются неразрешенными. Компоновщик разрешает неразрешенный символ, соединяя блок трансляции, который содержит неопределенный символ, вместе с тем, который содержит реализацию. Фух. Это верно для всех видимых извне символов, независимо от того, реализованы ли они в вашем коде или предоставлены дополнительной библиотекой. В действительности библиотека - это просто архив с повторно используемым кодом.
Есть два заметных исключения. Во-первых, если у вас есть небольшая функция, вы можете сделать ее встроенной. Это означает, что сгенерированный машинный код не генерирует вызов внешней функции, а буквально конкатенируется на месте. Поскольку они обычно небольшие, накладные расходы не имеют значения. Вы можете представить их статичными в том, как они работают. Таким образом, можно безопасно реализовать встроенные функции в заголовках. Реализации функций внутри определения класса или структуры также часто автоматически встраиваются компилятором.
Другое исключение - шаблоны. Поскольку компилятору необходимо видеть все определение типа шаблона при их создании, невозможно отделить реализацию от определения, как с автономными функциями или обычными классами. Что ж, возможно, сейчас это возможно, но для получения широкой поддержки компилятором ключевого слова «экспорт» потребовалось много времени. Итак, без поддержки «экспорта» единицы перевода получают свои собственные локальные копии созданных шаблонных типов и функций, аналогично тому, как работают встроенные функции. С поддержкой «экспорта» это не так.
За этими двумя исключениями некоторые люди находят «лучше» помещать реализации встроенных функций, шаблонных функций и шаблонных типов в файлы .cpp, а затем #include файл .cpp. Не имеет значения, заголовок это или исходный файл; препроцессору все равно, это просто соглашение.
Краткое описание всего процесса от кода C ++ (несколько файлов) до окончательного исполняемого файла:
Опять же, это было определенно больше, чем вы просили, но я надеюсь, что мелкие детали помогут вам увидеть более широкую картину.
1132441]
Think of cpp files as a black box and the .h files as the guides on how to use those black boxes.
The cpp files can be compiled ahead of time. This doesn't work in you #include them, as it needs to actual "include" the code into your program each time it compiles it. If you just include the header, it can just use the header file to determine how to use the precompiled cpp file.
Although this won't make much of a difference for your first project, if you start writing large cpp programs, people are going to hate you because compile times are going to explode.
Also have a read of this: Header File Include Patterns
The typical solution is to use .h
files for declarations only and .cpp
files for implementation. If you need to reuse the implementation you include the corresponding .h
file into the .cpp
file where the necessary class/function/whatever is used and link against an already compiled .cpp
file (either an .obj
file - usually used within one project - or .lib file - usually used for reusing from multiple projects). This way you don't need to recompile everything if only the implementation changes.
Header files usually contain declarations of functions / classes, while .cpp files contain the actual implementations. At compile time, each .cpp file gets compiled into an object file (usually extension .o), and the linker combines the various object files into the final executable. The linking process is generally much faster than the compilation.
Benefits of this separation: If you are recompiling one of the .cpp files in your project, you don't have to recompile all the others. You just create the new object file for that particular .cpp file. The compiler doesn't have to look at the other .cpp files. However, if you want to call functions in your current .cpp file that were implemented in the other .cpp files, you have to tell the compiler what arguments they take; that is the purpose of including the header files.
Disadvantages: When compiling a given .cpp file, the compiler cannot 'see' what is inside the other .cpp files. So it doesn't know how the functions there are implemented, and as a result cannot optimize as aggressively. But I think you don't need to concern yourself with that just yet (:
The basic idea that headers are only included and cpp files are only compiled. This will become more useful once you have many cpp files, and recompiling the whole application when you modify only one of them will be too slow. Or when the functions in the files will start depending on each other. So, you should separate class declarations into your header files, leave implementation in cpp files and write a Makefile (or something else, depending on what tools are you using) to compile the cpp files and link the resulting object files into a program.
If you #include a cpp file in several other files in your program, the compiler will try to compile the cpp file multiple times, and will generate an error as there will be multiple implementations of the same methods.
Compilation will take longer (which becomes a problem on large projects), if you make edits in #included cpp files, which then force recompilation of any files #including them.
Just put your declarations into header files and include those (as they don't actually generate code per se), and the linker will hook up the declarations with the corresponding cpp code (which then only gets compiled once).
While it is certainly possible to do as you did, the standard practice is to put shared declarations into header files (.h), and definitions of functions and variables - implementation - into source files (.cpp).
As a convention, this helps make it clear where everything is, and makes a clear distinction between interface and implementation of your modules. It also means that you never have to check to see if a .cpp file is included in another, before adding something to it that could break if it was defined in several different units.
возможность повторного использования, архитектура и инкапсуляция данных
вот пример:
скажем, вы создаете файл cpp, который содержит простую форму строковых подпрограмм, все в классе mystring, вы помещаете класс decl для этого в mystring.h, компилирующий mystring.cpp в файл .obj
, теперь в вашей основной программе (например, main.cpp) вы включаете заголовок и связываете с mystring.obj. чтобы использовать mystring в вашей программе, вам не нужны детали как mystring реализована, так как заголовок говорит , что он может делать
сейчас, если друг хочет использовать вашу mystring class, который вы даете ему mystring.h и mystring.obj, ему также не обязательно знать, как он работает, пока он работает.
позже, если у вас будет больше таких файлов .obj, вы можете объединить их в файл. lib и укажите ссылку на него.
вы также можете изменить файл mystring.cpp и реализовать его более эффективно, это не повлияет на ваш main.cpp или программу ваших друзей.
When you compile and link a program the compiler first compiles the individual cpp files and then they link (connect) them. The headers will never get compiled, unless included in a cpp file first.
Typically headers are declarations and cpp are implementation files. In the headers you define an interface for a class or function but you leave out how you actually implement the details. This way you don't have to recompile every cpp file if you make a change in one.
Я предлагаю вам пройти через Large Scale C ++ Software Design by John Lakos ]. В колледже мы обычно пишем небольшие проекты, где не сталкиваемся с такими проблемами. В книге подчеркивается важность разделения интерфейсов и реализаций. Точно так же изучение шаблонов, таких как идиома Virtual Constructor, поможет вам лучше понять эту концепцию.
Я все еще учусь, как и вы :)
Допустим, вы пишете книгу. Если вы поместите главы в отдельные файлы, вам нужно будет распечатать главу, только если вы изменили ее. Работа над одной главой не меняет других.
Но включение файлов cpp, с точки зрения компилятора, как редактирование всех глав книги в одном файле. Затем, если вы его измените, вам придется распечатать все страницы всей книги, чтобы напечатать исправленную главу. В генерации объектного кода нет опции «печатать выбранные страницы».
Вернемся к программному обеспечению: у меня есть Linux и Ruby src. Грубая мера строк кода ...
Linux Ruby
100,000 100,000 core functionality (just kernel/*, ruby top level dir)
10,000,000 200,000 everything
Любая из этих четырех категорий содержит много кода, отсюда и необходимость модульности. Такой вид кодовой базы на удивление типичен для реальных систем.
Linux Ruby
100,000 100,000 core functionality (just kernel/*, ruby top level dir)
10,000,000 200,000 everything
Любая из этих четырех категорий содержит много кода, отсюда и необходимость модульности. Такой вид кодовой базы на удивление типичен для реальных систем.
Linux Ruby
100,000 100,000 core functionality (just kernel/*, ruby top level dir)
10,000,000 200,000 everything
Любая из этих четырех категорий содержит много кода, отсюда и необходимость модульности. Такой вид кодовой базы на удивление типичен для реальных систем.
Если это сработает для вас, то в этом нет ничего плохого - за исключением того, что это будет трепать перья людей, которые думают, что есть только один способ делать что-то.
Многие из них. приведенные здесь ответы относятся к оптимизации крупномасштабных программных проектов. Об этом нужно знать, но нет смысла оптимизировать небольшой проект, как если бы это был большой проект - это то, что известно как «преждевременная оптимизация». В зависимости от вашей среды разработки может возникнуть значительная дополнительная сложность, связанная с настройкой конфигурации сборки для поддержки нескольких исходных файлов для каждой программы.
Если со временем ваш проект развивается, и вы обнаружите, что процесс сборки занимает слишком много времени, затем вы можете реорганизовать свой код, чтобы использовать несколько исходных файлов для более быстрой инкрементной сборки.
В некоторых ответах обсуждается разделение интерфейса от реализации. Однако это не является неотъемлемой особенностью включаемых файлов, и довольно часто #include «заголовочные» файлы напрямую включают их реализацию (даже Стандартная библиотека C ++ делает это в значительной степени).
Единственное, что действительно есть «Нетрадиционным» в отношении того, что вы сделали, было наименование ваших включенных файлов «.cpp» вместо «.h» или «.hpp».