C ++ код в заголовочных файлах - PullRequest
173 голосов
/ 24 февраля 2009

Мой личный стиль в C ++ всегда заключался в том, чтобы помещать объявления классов во включаемый файл и определения в файл .cpp, очень похоже на то, как указано в Ответ Локи на Заголовочные файлы C ++, Разделение кода . По общему признанию, одна из причин, по которой мне нравится этот стиль, вероятно, связана со всеми годами, которые я потратил на кодирование Modula-2 и Ada, оба из которых имеют похожую схему с файлами спецификаций и файлами тела.

У меня есть коллега, гораздо более осведомленный в C ++, чем я, который настаивает на том, чтобы все объявления C ++, по возможности, включали определения прямо в заголовочный файл. Он не говорит, что это допустимый альтернативный стиль или даже немного лучший стиль, а скорее это новый общепринятый стиль, который все сейчас используют для C ++.

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

Просто, чтобы дать некоторую структуру для ответов: это сейчас Путь , очень распространенный, несколько распространенный, необычный или ненормальный?

Ответы [ 17 ]

191 голосов
/ 24 февраля 2009

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

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

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

Итог, вы были правы, он не прав.

РЕДАКТИРОВАТЬ: Я думал о вашем вопросе. Существует один случай, когда то, что он говорит, является правдой. шаблоны. Многие новые «современные» библиотеки, такие как boost, интенсивно используют шаблоны и часто являются «только заголовками». Однако это следует делать только при работе с шаблонами, поскольку это единственный способ сделать это при работе с ними.

РЕДАКТИРОВАТЬ: Некоторые люди хотели бы немного больше разъяснений, вот некоторые мысли о минусах написания кода "только заголовок":

Если вы будете искать вокруг, вы увидите, что довольно много людей пытаются найти способ сократить время компиляции при работе с boost. Например: Как сократить время компиляции с Boost Asio , который видит компиляцию 14 с одного файла 1K с включенным Boost. Может показаться, что 14-ые не «взрываются», но они, безусловно, намного длиннее обычных и могут складываться довольно быстро. При работе с большим проектом. Библиотеки только с заголовками влияют на время компиляции весьма измеримым образом. Мы просто терпим это, потому что повышение очень полезно.

Кроме того, есть много вещей, которые нельзя сделать только в заголовках (даже в boost есть библиотеки, на которые нужно ссылаться для определенных частей, таких как потоки, файловая система и т. Д.). Основным примером является то, что вы не можете иметь простые глобальные объекты в заголовках только libs (если вы не прибегаете к мерзости, которая является одиночной), так как вы столкнетесь с множественными ошибками определения. ПРИМЕЧАНИЕ: Встроенные переменные C ++ 17 сделают этот конкретный пример выполнимым в будущем.

В заключение, при использовании boost в качестве примера кода, содержащего только заголовки, часто пропускаются огромные детали.

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

142 голосов
/ 24 февраля 2009

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

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

24 голосов
/ 24 февраля 2009

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

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

19 голосов
/ 24 февраля 2009

То, что может сообщить вашему коллеге, - это представление о том, что большая часть кода C ++ должна быть спроектирована так, чтобы обеспечить максимальное удобство использования. И если это шаблонно, то все должно быть в заголовочном файле, чтобы клиентский код мог его увидеть и создать экземпляр. Если этого достаточно для Boost и STL, для нас этого достаточно.

Я не согласен с этой точкой зрения, но, возможно, именно откуда она исходит.

12 голосов
/ 25 февраля 2009

Часто я помещаю тривиальные функции-члены в заголовочный файл, чтобы позволить им быть встроенными. Но поместить туда весь объем кода, просто чтобы соответствовать шаблонам? Это просто орехи.

Помните: Глупая последовательность - это хобгоблин маленьких умов .

12 голосов
/ 11 августа 2015

Я думаю, что ваш коллега умен и вы тоже правы.

Полезные вещи, которые я обнаружил, помещая все в заголовки, таковы:

  1. Нет необходимости писать и синхронизировать заголовки и источники.

  2. Структура проста, и круговые зависимости не заставляют кодера создавать "лучшую" структуру.

  3. Переносим, ​​легко встраиваем в новый проект.

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

  1. Смена исходного файла очень вероятно изменит заголовочные файлы, что приведет к повторной компиляции всего проекта.

  2. Скорость компиляции намного выше, чем раньше. И если у вас есть проект, который будет строиться с длительной и высокой частотой, это может указывать на недостатки вашего проекта. Разделите задачи в разные проекты, и модуль может избежать этой проблемы.

Наконец, я просто хочу поддержать вашего коллегу, просто по моему личному мнению.

6 голосов
/ 26 марта 2010

Как сказал Туомас, ваш заголовок должен быть минимальным. Для полноты я немного расширю.

Я лично использую 4 типа файлов в моих C++ проектах:

  • Public:
  • Заголовок пересылки: в случае шаблонов и т. Д. Этот файл получает объявления переадресации, которые появятся в заголовке.
  • Заголовок: этот файл включает заголовок переадресации, если таковой имеется, и объявляет все, что я хочу, чтобы он был открытым (и определяет классы ...)
  • Частный:
  • Закрытый заголовок: этот файл является заголовком, зарезервированным для реализации, он включает заголовок и объявляет вспомогательные функции / структуры (например, для Pimpl или предикатов). Пропустить, если не нужно.
  • Исходный файл: он включает частный заголовок (или заголовок, если нет частного заголовка) и определяет все (не шаблон ...)

Более того, я связываю это с другим правилом: не определяйте, что вы можете объявить. Хотя, конечно, я разумен (использование Pimpl везде довольно хлопотно).

Это означает, что я предпочитаю предварительное объявление над директивой #include в моих заголовках всякий раз, когда я могу сойти с рук.

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

В целом:

// example_fwd.hpp
// Here necessary to forward declare the template class,
// you don't want people to declare them in case you wish to add
// another template symbol (with a default) later on
class MyClass;
template <class T> class MyClassT;

// example.hpp
#include "project/example_fwd.hpp"

// Those can't really be skipped
#include <string>
#include <vector>

#include "project/pimpl.hpp"

// Those can be forward declared easily
#include "project/foo_fwd.hpp"

namespace project { class Bar; }

namespace project
{
  class MyClass
  {
  public:
    struct Color // Limiting scope of enum
    {
      enum type { Red, Orange, Green };
    };
    typedef Color::type Color_t;

  public:
    MyClass(); // because of pimpl, I need to define the constructor

  private:
    struct Impl;
    pimpl<Impl> mImpl; // I won't describe pimpl here :p
  };

  template <class T> class MyClassT: public MyClass {};
} // namespace project

// example_impl.hpp (not visible to clients)
#include "project/example.hpp"
#include "project/bar.hpp"

template <class T> void check(MyClass<T> const& c) { }

// example.cpp
#include "example_impl.hpp"

// MyClass definition

В данном случае спасатель заключается в том, что в большинстве случаев прямой заголовок бесполезен: необходим только в случае typedef или template, как и заголовок реализации;)

5 голосов
/ 25 февраля 2009

Как правило, при написании нового класса я помещаю весь код в класс, поэтому мне не нужно искать его в другом файле. После того, как все работает, я разбиваю тело методов на файл cpp, оставив прототипы в файле hpp.

5 голосов
/ 24 февраля 2009

Чтобы добавить больше удовольствия, вы можете добавить .ipp файлы, которые содержат реализацию шаблона (которая включена в .hpp), в то время как .hpp содержит интерфейс.

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

4 голосов
/ 24 февраля 2009

Я лично делаю это в моих заголовочных файлах:

// class-declaration

// inline-method-declarations

Мне не нравится смешивать код для методов с классом, так как мне трудно быстро искать вещи.

Я бы не помещал ВСЕ методы в заголовочный файл. Компилятор (обычно) не сможет встроить виртуальные методы и (вероятно) только встроенные небольшие методы без циклов (полностью зависит от компилятора).

Использование методов в классе допустимо ... но с точки зрения читабельности мне это не нравится. Помещение методов в заголовок означает, что, когда это возможно, они будут встроены.

...