Вопрос о заголовочном файле C ++ - PullRequest
3 голосов
/ 26 июня 2010

Я пробовал немного кода на С ++, работая с классами, и этот вопрос возник у меня, и он меня немного беспокоит.

Я создал заголовочный файл, который содержит определение моего класса и файл cpp, содержащий реализацию.

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

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

Извините, если это глупый вопрос, мне искренне интересно узнать, почему большинство людей включают файлы .h вместо .cpp, когда последние кажутся более естественными (я немного знаю PythonМожет быть, поэтому это кажется мне естественным, по крайней мере).Это просто история или есть техническая причина, касающаяся организации программы или, может быть, что-то еще?

Ответы [ 3 ]

13 голосов
/ 26 июня 2010

Потому что, когда вы компилируете другой файл, C ++ на самом деле не нужно знать о реализации.Ему нужно знать только подпись каждой функции (какие параметры она принимает и что она возвращает), имя каждого класса, что такое макросы #define d, и другую "сводную" информацию, подобную этой,так что он может проверить, что вы используете функции и классы правильно.Содержимое различных файлов .cpp не собирается, пока не будет запущен компоновщик.

Например, скажем, у вас есть foo.h

int foo(int a, float b);

и foo.cpp

#include "foo.h"
int foo(int a, float b) { /* implementation */ }

и bar.cpp

#include "foo.h"
int bar(void) {
    int c = foo(1, 2.1);
}

Когда вы компилируете foo.cpp, он становится foo.o, а когда вы компилируете bar.cpp, он становится bar.o.Теперь, в процессе компиляции, компилятор должен проверить, что определение функции foo() в foo.cpp согласуется с использованием функции foo() в bar.cpp (то есть принимает int и floatи возвращает int).Это достигается тем, что вы включаете один и тот же заголовочный файл в оба .cpp файла, и если и определение, и использование согласуются с объявлением в заголовке, то они должны согласовываться друг с другом.

Но компилятор на самом деле не включает реализацию foo() в bar.o.Он включает в себя инструкцию на ассемблере call foo.Поэтому, когда он создает bar.o, ему не нужно ничего знать о содержимом foo.cpp.Однако, когда вы переходите к этапу компоновки (который происходит после компиляции), компоновщику действительно нужно знать о реализации foo(), потому что он собирается включить эту реализацию в конечную программу и заменить инструкцию call foo наcall 0x109d9829 (или как он решает, адрес памяти функции foo() должен быть).

Обратите внимание, что компоновщик не проверяет, что реализация foo()foo.o) согласен с использованием foo()bar.o) - например, он не проверяет, что foo() вызывается с параметрами int и float!Довольно сложно выполнить такую ​​проверку на ассемблере (по крайней мере, сложнее, чем проверить исходный код C ++), поэтому компоновщик полагается на знание того, что компилятор уже проверил это.И именно поэтому вам нужен заголовочный файл, чтобы предоставить эту информацию компилятору.

1 голос
/ 26 июня 2010

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

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

0 голосов
/ 26 июня 2010

Одной из технических причин является скорость компиляции.Давайте предположим, что ваш класс использует 10 других классов (например, как типы для переменных-членов).Включение длинных файлов .cpp для всех 10 классов сделает ваш класс компиляцией намного медленнее (т. Е. Может быть, 2 секунды вместо 1 секунды).

Другая причина - скрытие реализации.Предположим, вы пишете класс, который будет использоваться 10 другими командами в вашей компании.Все, что они должны знать и узнавать о вашем классе, находится в файле .h (открытый интерфейс).Вы можете свободно делать все что угодно в файле .cpp (реализация), вы можете изменять его так часто, как вам хочется, им все равно.Но если вы измените файл .h, им, возможно, придется скорректировать свой код, используя ваш класс.

Для каждого тела метода вы можете выбрать, поместить его в файл .h или в файл .cpp.Если он находится в файле .h, компилятор может встроить его при вызове, что может сделать код немного быстрее.Но компиляция будет медленнее, и временные файлы .o (.obj) могут стать больше (потому что каждый из них будет содержать тело скомпилированного метода), а двоичный файл программы (.exe) может стать больше, потому что тело функции занимает местостолько раз это указывается.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...