Код С++ в файлах заголовков
Мой личный стиль с С++ всегда должен помещать объявления классов в include файл и определения в .cpp файле, что очень похоже на Loki ответ на С++ Header Files, Code Разделение. По общему признанию, часть причины, по которой мне нравится этот стиль, вероятно, имеет отношение ко всем годам, которые я потратил на кодирование Modula-2 и Ada, оба из которых имеют аналогичную схему со спецификационными файлами и файлами тела.
У меня есть коллега, гораздо более осведомленный на С++, чем я, который настаивает на том, чтобы все объявления С++, по возможности, включали определения там в заголовочном файле. Он не говорит, что это действительный альтернативный стиль или даже немного лучший стиль, но скорее это новый универсально принятый стиль, который все теперь используют для С++.
Я не такой разительный, как раньше, поэтому я не очень-то стараюсь подкрасться к этой походке, пока не увижу там еще нескольких человек. Так насколько распространена эта идиома?
Просто, чтобы дать некоторую структуру ответам: теперь ли это The Way, очень распространенный, несколько распространенный, необычный или безумный?
Ответы
Ответ 1
Ваш коллега ошибается, обычный способ и всегда заключался в том, чтобы вводить код в .cpp файлы (или любое другое расширение, которое вам нравится) и декларации в заголовках.
Иногда из-за некоторого достоинства класть код в заголовок, это может привести к более умному встраиванию компилятором. Но в то же время он может уничтожить ваши времена компиляции, так как весь код должен обрабатываться каждый раз, когда он включен компилятором.
Наконец, часто бывает неприятно иметь круговые отношения объектов (иногда желательно), когда весь код является заголовком.
В нижней строке, вы были правы, он ошибается.
EDIT: Я думал о вашем вопросе. Существует один случай, когда он говорит правду. шаблоны. Многие новые "современные" библиотеки, такие как boost, сильно используют шаблоны и часто являются "только заголовками". Однако это нужно делать только при работе с шаблонами, поскольку это единственный способ сделать это при работе с ними.
РЕДАКТИРОВАТЬ: Некоторые люди хотели бы получить немного больше разъяснений, вот некоторые мысли о нижних строках для написания кода "только заголовок":
Если вы будете искать вокруг, вы увидите довольно много людей, пытающихся найти способ сократить время компиляции при работе с boost. Например: Как сократить время компиляции с помощью Boost Asio, в котором содержится 14-секундная компиляция одного 1K файла с включенным boost. 14s может показаться не "взрывающимся", но это, безусловно, намного дольше, чем обычно, и может складываться довольно быстро. При работе с большим проектом. Библиотеки только для заголовков влияют на время компиляции вполне измеримым образом. Мы просто терпим это, потому что повышение настолько полезно.
Кроме того, есть много вещей, которые не могут быть выполнены только в заголовках (даже у boost есть библиотеки, для которых вам необходимо связать определенные части, такие как потоки, файловая система и т.д.). Первичный пример состоит в том, что вы не можете иметь простые глобальные объекты в только заголовках заголовков (если только вы не прибегаете к мерзости, которая является синглом), поскольку вы столкнетесь с несколькими ошибками определения. ПРИМЕЧАНИЕ. Встроенные переменные С++ 17 сделают этот конкретный пример выполнимым в будущем.
В качестве конечной точки при использовании boost в качестве примера кода только для заголовка огромная деталь часто пропускается.
Boost - это библиотека, а не код пользовательского уровня. поэтому это часто не меняется. В коде пользователя, если вы поместите все в заголовки, каждое небольшое изменение заставит вас перекомпилировать весь проект. Это монументальная трата времени (и не для библиотек, которые не меняются от компиляции до компиляции). Когда вы разделяете вещи между заголовком/источником и еще лучше, используйте декларации для сокращения, включая сокращение, вы можете сэкономить часы перекомпиляции, добавив их через день.
Ответ 2
В день, когда кодеры С++ соглашаются на The Way, ягнята лягут со львами, палестинцы будут обнимать израильтян, а кошкам и собакам будет разрешено выйти замуж.
Разделение между файлами .h и .cpp в большинстве случаев является произвольным, а остальная часть оптимизаций компилятора - в прошлом. На мой взгляд, объявления принадлежат заголовку, а определения принадлежат файлу реализации. Но это просто привычка, а не религия.
Ответ 3
Код в заголовках обычно является плохой идеей, так как он перекомпиляции всех файлов, которые содержат заголовок, когда вы меняете фактический код, а не декларации. Это также замедлит компиляцию, так как вам нужно проанализировать код в каждом файле, который включает заголовок.
Причиной иметь код в файлах заголовков является то, что обычно необходимо, чтобы ключевое слово inline работало правильно и при использовании шаблонов, которые были установлены в других файлах cpp.
Ответ 4
Что может сообщить вам, что коллега - это понятие о том, что большинство С++-кода должны быть шаблонами для максимального удобства использования. И если он будет изменен, тогда все должно быть в файле заголовка, чтобы клиентский код мог его увидеть и создать. Если это будет достаточно для Boost и STL, это будет достаточно для нас.
Я не согласен с этой точкой зрения, но может быть, откуда она исходит.
Ответ 5
Часто я помещаю тривиальные функции-члены в заголовочный файл, чтобы они были встроены. Но чтобы разместить весь код кода, просто чтобы соответствовать шаблонам? Эти простые орехи.
Помните: Глупая последовательность - это хобгоблин маленьких умов.
Ответ 6
Я думаю, что ваш коллега умный, и вы тоже правы.
Полезные вещи, которые я обнаружил, что помещение всех в заголовки состоит в том, что:
-
Не нужно писать и синхронизировать заголовки и источники.
-
Структура простая, и никакие круговые зависимости не приводят кодер к созданию "лучшей" структуры.
-
Портативный, простой встраивание в новый проект.
Я согласен с проблемой времени компиляции, но я думаю, что мы должны заметить, что:
-
Смена исходного файла, скорее всего, изменит файлы заголовков, что приведет к повторной компиляции всего проекта.
-
Скорость компиляции намного быстрее, чем раньше. И если у вас есть проект, который будет построен с долгой и высокой частотой, это может означать, что ваш проект имеет недостатки. Разделите задачи на разные проекты и модуль, чтобы избежать этой проблемы.
Наконец, я просто хочу поддержать своего коллегу, только на мой взгляд.
Ответ 7
Как сказал Туомас, ваш заголовок должен быть минимальным. Чтобы быть полным, я немного расширюсь.
Я лично использую 4 типа файлов в проектах C++
:
- Public:
- Переадресация заголовка: в случае шаблонов и т.д. этот файл получает объявления пересылки, которые будут отображаться в заголовке.
- Заголовок: этот файл содержит заголовок пересылки, если он есть, и объявляет все, что я хочу быть общедоступным (и определяет классы...)
- Private:
- Закрытый заголовок: этот файл является заголовком, зарезервированным для реализации, он включает заголовок и объявляет вспомогательные функции/структуры (например, для 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
, а также заголовок реализации;)
Ответ 8
Я лично делаю это в своих заголовочных файлах:
// class-declaration
// inline-method-declarations
Мне не нравится смешивать код для методов с классом, поскольку я нахожу, что это боль, чтобы быстро разобраться.
Я бы не поместил ВСЕ методы в файл заголовка. Компилятор (обычно) не сможет встроить виртуальные методы и будет (скорее всего) только встроенными малыми методами без циклов (полностью зависит от компилятора).
Выполнение методов в классе допустимо... но с точки зрения readablilty мне это не нравится. Помещение методов в заголовок означает, что, когда это возможно, они будут вставлены.
Ответ 9
Если этот новый способ действительно The Way, мы могли бы работать в разных направлениях в наших проектах.
Потому что мы стараемся избегать всех ненужных вещей в заголовках. Это включает предотвращение каскада заголовков. Код в заголовках будет нуждаться в некотором другом заголовке, который будет включен, что потребует другого заголовка и так далее. Если мы вынуждены использовать шаблоны, мы стараемся избегать слишком больших завалов заголовков с помощью шаблонов.
Также мы используем "opaque pointer" -pattern, когда это применимо.
С помощью этих методов мы можем делать более быстрые сборки, чем большинство наших сверстников. И да... изменение кода или членов класса не вызовет огромных перестроек.
Ответ 10
Чтобы добавить больше удовольствия, вы можете добавить .ipp
файлы, которые содержат реализацию шаблона (который включен в .hpp
), а .hpp
содержит интерфейс.
Помимо шаблонизированного кода (в зависимости от проекта это может быть большинство или меньшинство файлов) есть нормальный код, и здесь лучше отделить объявления и определения. Предоставляйте также форвардные объявления там, где это необходимо - это может повлиять на время компиляции.
Ответ 11
Как правило, при написании нового класса я помещаю весь код в класс, поэтому мне не нужно искать его в другом файле. После того, как все работает, я разбиваю тело методов на файл cpp, оставляя прототипы в файле hpp.
Ответ 12
ИМХО, у него есть заслуга ТОЛЬКО, если он делает шаблоны и/или метапрограммирование. Есть много причин, о которых уже упоминалось, что вы ограничиваете заголовочные файлы только декларациями. Они просто... заголовки. Если вы хотите включить код, вы скомпилируете его как библиотеку и соедините его.
Ответ 13
Я выложил всю реализацию из определения класса. Я хочу, чтобы комментарии doxygen были исключены из определения класса.
Ответ 14
Разве это не зависит от сложности системы и внутренних соглашений?
В настоящий момент я работаю над симулятором нейронной сети, который невероятно сложный, и принятый стиль, который я ожидаю использовать:
Определения классов в classname.h
Код класса в classnameCode.h
исполняемый код в classname.cpp
Это разбивает пользовательские симуляции из базовых классов, созданных разработчиками, и работает лучше всего в этой ситуации.
Однако я был бы удивлен, если бы люди это сделали, скажем, графическое приложение или любое другое приложение, целью которого является не предоставление пользователям базы кода.
Ответ 15
Код шаблона должен быть только в заголовках. Кроме того, все определения, кроме строк, должны быть в .cpp. Лучшим аргументом для этого будет реализация библиотек std, которые следуют тому же правилу. Вы бы не согласились с тем, что разработчики std lib были бы правы в этом отношении.
Ответ 16
Я думаю, что ваш коллега прав, если он не входит в процесс для написания исполняемого кода в заголовке.
Правильный баланс, я думаю, должен следовать по пути, указанному GNAT Ada, где файл .ads дает совершенно правильное определение интерфейса пакета для его пользователей и для его дочерних элементов.
Кстати, как вы обращались на этом форуме к недавнему вопросу о привязке Ada к библиотеке CLIPS, которую вы написали несколько лет назад и которая уже недоступна (соответствующие веб-страницы теперь закрыты). Даже если это сделано в старой версии Clips, эта привязка может стать хорошим примером для тех, кто хочет использовать механизм вывода CLIPS в рамках программы Ada 2012.
Ответ 17
Я думаю, что абсолютно абсурдно помещать ВСЕ ваши определения функций в заголовочный файл. Зачем? Поскольку заголовочный файл используется как PUBLIC-интерфейс для вашего класса. Это снаружи "черного ящика".
Когда вам нужно взглянуть на класс, чтобы ссылаться на его использование, вы должны посмотреть на файл заголовка. Заголовочный файл должен предоставить список того, что он может сделать (комментарий для описания деталей использования каждой функции), и он должен включать список переменных-членов. Это НЕ ДОЛЖНО включать КАК каждая индивидуальная функция реализована, потому что лодка загружает ненужную информацию и только загромождает заголовочный файл.