Код C ++ в файлах заголовков
Мой личный стиль с C ++ всегда должен включать объявления classов в include-файл и определения в файле .cpp, что очень похоже на ответ Loki на заголовки C ++ Header Files, Code Separation . По общему признанию, часть причины, по которой мне нравится этот стиль, вероятно, связана со всеми годами, которые я потратил на кодирование Modula-2 и Ada, оба из которых имеют аналогичную схему со спецификационными файлами и файлами body.
У меня есть коллега, гораздо более осведомленный на C ++, чем я, который настаивает на том, чтобы все объявления C ++, по возможности, включали определения прямо в заголовочный файл. Он не говорит, что это действительный альтернативный стиль или даже немного лучший стиль, но это новый универсально принятый стиль, который все теперь используют для C ++.
Я не такой разительный, как раньше, поэтому я не очень стараюсь подкрасться к этой побеге, пока я не увижу там еще нескольких людей. Так насколько распространена эта идиома?
- Base64 кодирует строку в VBScript
- В чем разница между кодировкой и кодировкой?
- Использование библиотеки FFMPEG с iPhone SDK для кодирования видео
- RStudio не выбирает кодировку, которую я говорю ей при чтении файла
- Избавьтесь от уродливых утверждений
Просто чтобы дать некоторую структуру ответам: теперь это Путь , очень распространенный, несколько общий, необычный или безумный?
- Какую кодировку использует Microsoft Excel при сохранении файлов?
- Какой префикс вы используете для переменных-членов?
- Excel в CSV с кодировкой UTF8
- Что означает точка в R - личное предпочтение, соглашение об именовании или более?
- Как изменить кодировку по умолчанию в NetBeans 8.0
- Кодирование / декодирование массивов типов, соответствующих протоколу с JSONEncoder
- Как загрузить исходный () .R-файл с использованием кодировки UTF-8?
- Как определить кодировку файлов в OSX?
Ваш коллега ошибается, обычный способ и всегда заключался в том, чтобы вводить код в .cpp-файлы (или любое другое расширение, которое вам нравится) и объявления в заголовках.
Иногда бывает некоторая заслуга в том, чтобы помещать код в заголовок, это может позволить более умную встраивание компилятором. Но в то же время он может уничтожить ваши моменты компиляции, поскольку весь код должен обрабатываться каждый раз, когда он включается компилятором.
Наконец, часто бывает неприятно иметь круговые объектные отношения (иногда желательно), когда весь код является заголовком.
В итоге, вы были правы, он ошибается.
EDIT: Я думал о вашем вопросе. Есть один случай, когда он говорит правду. шаблоны. Многие новые «современные» библиотеки, такие как boost, сильно используют шаблоны и часто являются «только заголовками». Однако это нужно делать только при работе с шаблонами, поскольку это единственный способ сделать это при работе с ними.
EDIT: Некоторым людям хотелось бы получить немного больше разъяснений, вот некоторые мысли о нижних сторонах написания кода «только заголовок»:
Если вы будете искать вокруг, вы увидите довольно много людей, пытающихся найти способ сократить время компиляции при работе с boost. Например: Как сократить время компиляции с помощью Boost Asio , в котором содержится 14-секундная компиляция одного 1K-файла с включенным boost. 14s может показаться не «взрывающимся», но это, безусловно, намного дольше, чем обычно, и может складываться довольно быстро. При работе с большим проектом. Библиотеки только для заголовков влияют на время компиляции вполне измеримым образом. Мы просто терпим это, потому что повышение настолько полезно.
Кроме того, есть много вещей, которые не могут быть сделаны только в заголовках (даже boost имеет библиотеки, с которыми вам нужно ссылаться для определенных частей, таких как streamи, файловая система и т. Д.). Первичный пример состоит в том, что вы не можете иметь простые глобальные объекты в только заголовках заголовков (если только вы не прибегаете к мерзости, которая является синглом), поскольку вы столкнетесь с несколькими ошибками определения. ПРИМЕЧАНИЕ. Встроенные переменные C ++ 17 сделают этот конкретный пример выполнимым в будущем.
В качестве конечной точки, при использовании boost в качестве примера кода только заголовка, огромная деталь часто пропускается.
Boost – это библиотека, а не код уровня пользователя. поэтому это часто не меняется. В коде пользователя, если вы поместите все в заголовки, каждое небольшое изменение заставит вас перекомпилировать весь проект. Это монументальная трата времени (и не для библиотек, которые не меняются от компиляции до компиляции). Когда вы разделяете вещи между заголовком / источником и еще лучше, используйте декларации для сокращения, включая сокращения, вы можете сэкономить часы перекомпиляции, когда они складываются через день.
В день, когда кодеры C ++ соглашаются на Пути , ягнят лягут со львами, палестинцы будут обнимать израильтян, и кошки и собаки будут допущены к вступлению в брак.
Разделение между файлами .h и .ppp в большинстве случаев является произвольным, а остальная часть оптимизаций компилятора – давно. На мой взгляд, объявления принадлежат заголовку, а определения принадлежат файлу реализации. Но это просто привычка, а не религия.
Код в заголовках – это, как правило, плохая идея, поскольку он заставляет перекомпилировать все файлы, содержащие заголовок, когда вы меняете фактический код, а не декларации. Это также замедлит компиляцию, так как вам нужно проанализировать код в каждом файле, который включает заголовок.
Причина наличия кода в файлах заголовков заключается в том, что обычно необходимо, чтобы ключевое слово inline работало правильно, и при использовании шаблонов, которые устанавливаются в других файлах cpp.
Что может сообщить вам коллега, это представление о том, что большинство C ++-кода должны быть шаблонами для максимального удобства использования. И если это шаблон, тогда все должно быть в файле заголовка, чтобы клиентский код мог его увидеть и создать. Если это достаточно для Boost и STL, для нас это достаточно хорошо.
Я не согласен с этой точкой зрения, но может быть, откуда она исходит.
Я думаю, что ваш коллега умный, и вы тоже верны.
Полезные вещи, которые я обнаружил, что все, что в заголовках, заключается в следующем:
-
Нет необходимости писать и синхронизировать заголовки и источники.
-
Структура простая, и никакие круговые зависимости не заставляют кодера создавать «лучшую» структуру.
-
Портативный, легко встроенный в новый проект.
Я согласен с проблемой времени компиляции, но я думаю, что мы должны заметить, что:
-
Изменение исходного файла, скорее всего, изменит файлы заголовков, что приведет к повторной компиляции всего проекта.
-
Скорость компиляции намного быстрее, чем раньше. И если у вас есть проект, который будет построен с долгой и высокой частотой, это может означать, что ваш проект имеет недостатки. Разделите задачи на разные проекты и модуль, чтобы избежать этой проблемы.
Наконец, я просто хочу поддержать своего коллегу, только на мой взгляд.
Часто я помещаю тривиальные функции-члены в заголовочный файл, чтобы они были встроены. Но чтобы разместить весь код кода, просто чтобы соответствовать шаблонам? Это простые орехи.
Помните: глупая консистенция – это хобгоблин маленьких умов .
Как правило, при написании нового classа я поместил весь код в class, поэтому мне не нужно искать его в другом файле. После того, как все работает, я разбиваю тело методов в файл cpp , оставив прототипы в файле hpp.
Как сказал Туомас, ваш заголовок должен быть минимальным. Чтобы быть полным, я немного расширюсь.
Я лично использую 4 типа файлов в моих проектах на C++
:
- Общественность:
- Переадресация заголовка: в случае шаблонов и т. Д. Этот файл получает объявления пересылки, которые будут отображаться в заголовке.
- Заголовок: этот файл содержит заголовок пересылки, если таковой имеется, и объявляет все, что я хочу быть общедоступным (и определяет classы …)
- Частный:
- Частный заголовок: этот файл является заголовком, зарезервированным для реализации, он включает заголовок и объявляет вспомогательные функции / структуры (например, для 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 MyClassT; // example.hpp #include "project/example_fwd.hpp" // Those can't really be skipped #include #include #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 mImpl; // I won't describe pimpl here :p }; template class MyClassT: public MyClass {}; } // namespace project // example_impl.hpp (not visible to clients) #include "project/example.hpp" #include "project/bar.hpp" template void check(MyClass const& c) { } // example.cpp #include "example_impl.hpp" // MyClass definition
Спасатель здесь состоит в том, что в большинстве случаев прямой заголовок бесполезен: необходим только в случае typedef
или template
а также заголовок реализации;)
Если этот новый способ действительно «Путь» , мы могли бы столкнуться с другим направлением в наших проектах.
Потому что мы стараемся избегать всех ненужных вещей в заголовках. Это включает предотrotation каскада заголовков. Код в заголовках будет нуждаться в некотором другом заголовке, который будет включен, что потребует другого заголовка и так далее. Если мы вынуждены использовать шаблоны, мы стараемся избегать слишком большого количества заголовков с материалами шаблонов.
Также, если применимо, мы используем «непрозрачный указатель» .
С помощью этой практики мы можем делать более быстрые сборки, чем большинство наших сверстников. И да … изменение кода или членов classа не вызовет огромных перестроек.
Чтобы добавить больше удовольствия, вы можете добавить файлы .ipp
которые содержат реализацию шаблона (который включен в .hpp
), в то время как .hpp
содержит интерфейс.
Помимо шаблонизированного кода (в зависимости от проекта это может быть большинство или меньшинство файлов) есть нормальный код, и здесь лучше отделить объявления и определения. Предоставляйте также форвардные объявления там, где это необходимо – это может повлиять на время компиляции.
Я лично делаю это в своих заголовочных файлах:
// class-declaration // inline-method-declarations
Мне не нравится смешивать код для методов с classом, поскольку я нахожу, что это боль, чтобы быстро разобраться.
Я бы не поставил ВСЕ методы в файл заголовка. Компилятор (обычно) не сможет встроить виртуальные методы и будет (скорее всего) только встроенными малыми методами без циклов (полностью зависит от компилятора).
Выполнение методов в classе действительно … но с точки зрения readablilty мне это не нравится. Помещение методов в заголовок означает, что, когда это возможно, они будут вставлены.
ИМХО, он заслуживает ТОЛЬКО ТОЛЬКО, если он делает шаблоны и / или метапрограммирование. Есть много причин, о которых уже упоминалось, что вы ограничиваете файлы заголовков только декларациями. Они просто … заголовки. Если вы хотите включить код, вы скомпилируете его как библиотеку и привяжите ее.
Я внедрил всю реализацию из определения classа. Я хочу, чтобы комментарии doxygen были исключены из определения classа.
Разве это не зависит от сложности системы и внутренних соглашений?
В настоящий момент я работаю над симулятором нейронной сети, который невероятно сложный, и принятый стиль, который я ожидаю использовать:
Определения classов в classname.h
Код classа в classnameCode.h
исполняемый код в classname.cpp
Это разбивает пользовательские симуляции из базовых classов, созданных разработчиками, и работает лучше всего в ситуации.
Тем не менее, я был бы удивлен, если бы люди это сделали, скажем, графическое приложение или любое другое приложение, целью которого является не предоставление пользователям базы кода.
Код шаблона должен быть только в заголовках. Кроме того, все определения, кроме строк, должны быть в .cpp. Лучшим аргументом для этого будет реализация библиотек std, которые следуют тому же правилу. Вы бы не согласились с тем, что разработчики std lib были бы правы в этом отношении.
Я думаю, что ваш коллега прав, пока он не входит в процесс для написания исполняемого кода в заголовке. Правильный баланс, я думаю, должен следовать по пути, указанному GNAT Ada, где файл .ads дает совершенно правильное определение интерфейса пакета для его пользователей и для его дочерних элементов.
Кстати, Тед, посмотрел ли вы на этот форум недавний вопрос о привязке Ada к библиотеке CLIPS, которую вы написали несколько лет назад и которая больше недоступна (соответствующие веб-страницы теперь закрыты). Даже если это сделано в старой версии Clips, эта привязка может стать хорошим примером для тех, кто хочет использовать механизм вывода CLIPS в рамках программы Ada 2012.
Я думаю, что абсолютно абсурдно вносить ВСЕ определения ваших функций в заголовочный файл. Зачем? Поскольку заголовочный файл используется как PUBLIC-интерфейс для вашего classа. Это внешняя сторона «черного ящика».
Когда вам нужно взглянуть на class, чтобы указать, как его использовать, вы должны посмотреть заголовочный файл. Заголовочный файл должен предоставить список того, что он может сделать (комментарий для описания деталей использования каждой функции), и он должен включать список переменных-членов. Это НЕ ДОЛЖНО включать КАК каждая индивидуальная функция реализована, потому что это лодка на загрузке ненужной информации и только загромождает заголовочный файл.