C++ код в заголовочных файлах

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

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

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

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

14 ответов


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

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

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

итог, вы были правы, он ошибается.

EDIT: Я думал о вашем вопросе. Есть один случай, когда то, что он говорит, правда. шаблоны. Многие новые" современные " библиотеки, такие как boost, активно используют шаблоны и часто являются "только заголовками"."Однако это следует делать только при работе с шаблонами, поскольку это единственный способ сделать это при работе с их.

EDIT: некоторые люди хотели бы немного больше разъяснений, вот некоторые мысли о недостатках написания кода "только заголовок":

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

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

в качестве конечной точки, при использовании boost в качестве примера кода только заголовка, огромная деталь часто пропускается.

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


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

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


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

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


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

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


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

полезные вещи, которые я нашел, что положить все в заголовки это:

  1. Не нужно писать & заголовки синхронизации и источники.

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

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

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

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

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

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


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

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


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


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

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

  • общие:
  • заголовок пересылки: в случае шаблонов и т. д. Этот файл получает объявления пересылки, которые появятся в заголовке.
  • заголовок: этот файл включает заголовок пересылки, если таковой имеется, и объявляет все, что я хочу быть общедоступным (и определяет класс...)
  • личные:
  • Private header: этот файл является заголовком, зарезервированным для реализации, он включает заголовок и объявляет вспомогательные функции / структуры (например, для 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 и так же заголовок реализации;)


Если этот новый способ реально Путь, мы могли бы работать в другом направлении в наших проектах.

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

также мы используем "непрозрачный указатель"-шаблон когда это применимо.

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


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

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


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

// class-declaration

// inline-method-declarations

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

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

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


ИМХО, у него есть заслуга, только если он делает шаблоны и / или метапрограммирование. Существует множество причин, уже упомянутых, что вы ограничиваете заголовочные файлы только объявлениями. Они просто такие... заголовки. Если вы хотите включить код, вы компилируете его как библиотеку и связываете его.


Я поместил всю реализацию из определения класса. Я хочу иметь комментарии doxygen из определения класса.


разве это не зависит от сложности системы и внутренних соглашений?

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

определения классов в classname.h
Код класса в classnameCode.h
исполняемый код класса.cpp

Это разбивает построенные пользователем моделирования из базовых классов, построенных разработчиком, и работает лучший в данной ситуации.

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