Что должно быть в файле .h?


96

При разделении кода на несколько файлов, что именно должно помещаться в файл .h, а что - в файл .cpp?


1
Связанный вопрос: stackoverflow.com/questions/333889/…
Spoike

7
Это проблема чистого стиля, но я считаю, что объявления C ++ помещаются в .hppфайл, а объявления C - в .hфайл. Это очень полезно при смешивании кода C и C ++ (например, унаследованных модулей в C).
Томас Мэтьюз,

@ThomasMatthews Имеет смысл. Эта практика часто используется?
ty

@lightningleaf: Да, эта практика часто используется, особенно при смешивании языков C ++ и C.
Thomas Matthews

Ответы:


117

Заголовочные файлы ( .h) предназначены для предоставления информации, которая потребуется в нескольких файлах. Такие вещи, как объявления классов, прототипы функций и перечисления, обычно помещаются в файлы заголовков. Одним словом, «определения».

Файлы кода ( .cpp) предназначены для предоставления информации о реализации, которая должна быть известна только в одном файле. В общем, тела функций и внутренние переменные, которые не должны / никогда не должны быть доступны другим модулям, принадлежат .cppфайлам. Одним словом, «реализации».

Самый простой вопрос, который нужно задать себе, чтобы определить, что принадлежит и где находится: «Если я изменю это, мне придется изменить код в других файлах, чтобы снова компилировать вещи?» Если ответ «да», он, вероятно, принадлежит заголовочному файлу; если ответ «нет», он, вероятно, принадлежит файлу кода.


4
За исключением данных частного класса, которые должны идти в заголовок. Шаблоны должны быть полностью определены заголовком (если вы не используете один из немногих поддерживающих компиляторов export). Единственный способ обойти №1 - это PIMPL. # 2 был бы возможен, если бы exportподдерживался, и может быть возможно с использованием c ++ 0x и externшаблонов. IMO, файлы заголовков в С ++ теряют большую часть своей полезности.
KitsuneYMG

24
Все хорошо, но с неточной терминологией. Одним словом, «декларации» - термин «определение» является синонимом «реализации». В заголовке должен быть только декларативный код, встроенный код, определения макросов и код шаблона; т.е. ничего, что создает экземпляр кода или данных.
Клиффорд,

8
Я должен согласиться с Клиффордом. Вы используете декларацию и определение терминов довольно слабо и в некоторой степени взаимозаменяемо. Но они имеют точное значение в C ++. Примеры: объявление класса вводит имя класса, но не говорит, что в нем содержится. В определении класса перечислены все члены и дружественные функции. Оба могут быть без проблем помещены в файлы заголовков. То, что вы называете «прототипом функции», - это объявление функции . Но функция определение является то , что вещь , которая содержит код , функции и должны быть помещены в CPP-файл - если это не встроенное или (часть) шаблона.
sellibitze

5
У них есть точные значения в C ++, у них нет точных значений в английском языке. Мой ответ был написан в последнем.
Эмбер,

56

Дело в том, что в C ++ это несколько сложнее, чем организация заголовка / источника C.

Что видит компилятор?

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

Итак, зачем нужны заголовки?

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

В этом случае есть две копии одной и той же информации. Что зло ...

Решение - поделиться некоторыми подробностями. Хотя реализация должна оставаться в источнике, объявление общих символов, таких как функции, или определение структур, классов, перечислений и т. Д., Может потребоваться совместно.

Заголовки используются для размещения этих общих деталей.

Переместите в заголовок объявления о том, что нужно разделить между несколькими источниками

Ничего более?

В C ++ есть и другие вещи, которые можно поместить в заголовок, потому что они тоже должны быть общими:

  • встроенный код
  • шаблоны
  • константы (обычно те, которые вы хотите использовать внутри переключателей ...)

Переместите в заголовок ВСЕ, чем нужно поделиться, включая общие реализации

Значит ли это, что внутри заголовков могут быть источники?

Да. На самом деле, есть много разных вещей, которые могут быть внутри «заголовка» (т. Е. Разделены между источниками).

  • Форвардные декларации
  • объявления / определение функций / структур / классов / шаблонов
  • реализация встроенного и шаблонного кода

Это становится сложным, а в некоторых случаях (циклические зависимости между символами) невозможно сохранить его в одном заголовке.

Заголовки можно разбить на три части

Это означает, что в крайнем случае у вас могут быть:

  • заголовок форвардной декларации
  • заголовок объявления / определения
  • заголовок реализации
  • источник реализации

Представим, что у нас есть шаблонный объект MyObject. У нас могло быть:

// - - - - MyObject_forward.hpp - - - - 
// This header is included by the code which need to know MyObject
// does exist, but nothing more.
template<typename T>
class MyObject ;

.

// - - - - MyObject_declaration.hpp - - - - 
// This header is included by the code which need to know how
// MyObject is defined, but nothing more.
#include <MyObject_forward.hpp>

template<typename T>
class MyObject
{
   public :
      MyObject() ;
   // Etc.
} ;

void doSomething() ;

.

// - - - - MyObject_implementation.hpp - - - - 
// This header is included by the code which need to see
// the implementation of the methods/functions of MyObject,
// but nothing more.
#include <MyObject_declaration.hpp>

template<typename T>
MyObject<T>::MyObject()
{
   doSomething() ;
}

// etc.

.

// - - - - MyObject_source.cpp - - - - 
// This source will have implementation that does not need to
// be shared, which, for templated code, usually means nothing...
#include <MyObject_implementation.hpp>

void doSomething()
{
   // etc.
} ;

// etc.

Вот это да!

В «реальной жизни» это обычно не так сложно. Большая часть кода будет иметь только простую организацию заголовка / источника с некоторым встроенным кодом в исходном коде.

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

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

Вывод

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

Но в тот день, когда у вас возникнут циклические зависимости между шаблонными объектами, не удивляйтесь, если ваша организация кода станет несколько более "интересной", чем простая организация заголовка / источника ...

^ _ ^


18

в дополнение ко всем другим ответам я скажу вам, что вы НЕ помещаете в файл заголовка:
usingобъявление (наиболее распространенное using namespace std;) не должно появляться в файле заголовка, потому что они загрязняют пространство имен исходного файла, в который он включен .


+1 с оговоркой, что вы можете использовать его, если он находится в некотором подробном пространстве имен (или анонимном пространстве имен). Но да, никогда не используйте usingдля переноса чего-либо в глобальное пространство имен в заголовке.
KitsuneYMG

+1 На этот вопрос гораздо легче ответить. :) Также файлы заголовков не должны содержать анонимных пространств имен.
sellibitze

Заголовочные файлы могут содержать анонимные пространства имен, если вы понимаете, что это означает, т.е. что каждая единица перевода будет иметь другую копию материала, который вы определяете в пространстве имен. Встроенные функции в анонимных пространствах имен рекомендуются в C ++ для случаев, когда вы использовали бы static inlineв C99, потому что что-то связано с тем, что происходит, когда вы объединяете внутреннюю связь с шаблонами. Пространства имен Anon позволяют «скрывать» функции, сохраняя при этом внешнюю связь.
Стив Джессоп,

Стив, то, что ты написал, меня не убедило. Выберите конкретный пример, в котором, по вашему мнению, анонимное пространство имен имеет смысл в заголовочном файле.
sellibitze

7

То, что компилируется в ничто (нулевой двоичный след), попадает в файл заголовка.

Переменные не компилируются в ничто, но объявления типов делают (потому что они описывают только поведение переменных).

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

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


1
«встроенные функции ... создают код только там, где они вызываются». Это не правда. встроенные функции могут или не могут быть встроены в сайты вызовов, но даже если они встроены, реальное тело функции все еще существует, как и для не встроенной функции. Причина, по которой можно иметь встроенные функции в заголовках, не связана с тем, генерируют ли они код, это потому, что встроенные функции не запускают одно правило определения, поэтому, в отличие от не встроенных функций, нет проблем со связью вместе двух разных единиц перевода. которые оба включают заголовок.
Стив Джессоп,

4

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

Этот и похожие на него вопросы часто задаются в SO - см. Почему в C ++ используются файлы заголовков и файлы .cpp? и файлы заголовков C ++, например , разделение кода .


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

2

Объявления ваших классов и функций, а также документация и определения встроенных функций / методов (хотя некоторые предпочитают помещать их в отдельные файлы .inl).


2

В основном файл заголовка содержит скелет или объявление класса (не часто меняется)

а файл cpp содержит реализацию класса (часто меняется).


5
Пожалуйста, воздержитесь от использования нестандартной терминологии. Что такое «скелет класса», что «реализация класса»? Кроме того, то, что вы называете объявлением в контексте классов, вероятно, включает определения классов.
sellibitze

1

файл заголовка (.h) должен быть для объявлений классов, структур и их методов, прототипов и т.д. Реализация этих объектов выполняется в cpp.

в .h

    class Foo {
    int j;

    Foo();
    Foo(int)
    void DoSomething();
}

1

Я ожидал увидеть:

  • декларации
  • Комментарии
  • определения, отмеченные встроенными
  • шаблоны

на самом деле ответ заключается в том, что не нужно вставлять:

  • определения (могут привести к многократному определению вещей)
  • использование деклараций / директив (навязывает их любому, включая ваш заголовок, может вызвать столкновения имен)

1
Вы также можете поместить определения классов в файлы заголовков. Объявление класса ничего не говорит о его членах.
sellibitze

1

Заголовок что-то определяет, но ничего не говорит о реализации. (За исключением шаблонов в этом "метафоре".

С учетом сказанного, вам нужно разделить «определения» на подгруппы, в данном случае есть два типа определений.

  • Вы определяете «макет» своей структуры, сообщая только то, что необходимо окружающим группам использования.
  • Определения переменной, функции и класса.

Теперь я, конечно, говорю о первой подгруппе.

Заголовок предназначен для определения макета вашей структуры, чтобы помочь остальной части программного обеспечения использовать реализацию. Возможно, вы захотите увидеть это как «абстракцию» вашей реализации, что шутливо сказано, но я думаю, что в данном случае это вполне подходит.

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


1
  • Заголовочные файлы - не должны слишком часто меняться в процессе разработки -> стоит подумать и сразу написать их (в идеальном случае)
  • Исходные файлы - изменения в процессе внедрения

Это одна практика. Для некоторых небольших проектов это может быть подходящим вариантом. Но вы можете попробовать исключить функции и их прототипы (в файлах заголовков), а не изменять их подпись или удалять их. По крайней мере, до смены основного номера. Например, когда 1.9.2 был переведен на 2.0.0 beta.
TamusJRoyce

1

Заголовок (.h)

  • Макросы и включает необходимые для интерфейсов (как можно меньше)
  • Объявление функций и классов
  • Документация по интерфейсу
  • Объявление встроенных функций / методов, если есть
  • extern в глобальные переменные (если есть)

Тело (.cpp)

  • Остальные макросы и включают
  • Включите заголовок модуля
  • Определение функций и методов
  • Глобальные переменные (если есть)

Как правило, вы помещаете «совместно используемую» часть модуля в .h (часть, которую должны видеть другие модули), а «не разделяемую» часть в .cpp.

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

РЕДАКТИРОВАТЬ: изменено после комментария Дэвида


Как показывает практика, в файле .h должно быть как можно меньше включений, а файл .cpp должен включать все нужные заголовки. Это сокращает время компиляции и не загрязняет пространства имен.
Дэвид Торнли
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.