Как сделать заголовочный файл c

Урок №122. Классы и заголовочные файлы

Обновл. 10 Фев 2021 |

На этом уроке мы рассмотрим работу классов с заголовочными файлами в языке С++.

Отделение объявления от реализации

Все классы, которые мы использовали до сих пор, были достаточно простыми, поэтому мы записывали методы непосредственно внутри тела классов, например:

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

К счастью, язык C++ предоставляет способ отделить «объявление» от «реализации». Это делается путем определения методов вне тела самого класса. Для этого просто определите методы класса, как если бы они были обычными функциями, но в качестве префикса добавьте к имени функции имя класса с оператором разрешения области видимости ( :: ).

Вот наш класс Date с конструктором Date() и методом setDate(), определенными вне тела класса. Обратите внимание, прототипы этих функций все еще находятся внутри тела класса, но их фактическая реализация находится за его пределами:

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

Вот еще один пример класса с конструктором, определенным извне, со списком инициализации членов:

Классы и заголовочные файлы

Вопрос №1: «Разве определение класса в заголовочном файле не нарушает правило одного определения?».

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

Вопрос №2: «Разве определения методов класса в заголовочном файле не нарушает правило одного определения?».

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

Параметры по умолчанию

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

Библиотеки

Разделение объявления класса и его реализации очень распространено в библиотеках, которые используются для расширения возможностей вашей программы. Вы также подключали такие заголовочные файлы из Стандартной библиотеки С++, как iostream, string, vector, array и другие. Обратите внимание, вы не добавляли iostream.cpp, string.cpp, vector.cpp или array.cpp в ваши проекты. Ваша программа нуждается только в объявлениях из заголовочных файлов, чтобы компилятор смог проверить корректность вашего кода в соответствии с правилами синтаксиса языка C++. Однако реализации классов, находящихся в Стандартной библиотеке С++, содержатся в предварительно скомпилированном файле, который добавляется на этапе линкинга. Вы нигде не встречаете этот код.

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

Защита интеллектуальной собственности (создатели не хотят, чтобы другие просто «воровали» их код).

Заключение

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

Во-первых, как упоминалось выше, это приведет к загромождению определения вашего класса.

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

Поэтому рекомендуется следующее:

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

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

Источник

Руководство Google по стилю в C++. Часть 2

Часть 1. Вступление
Часть 2. Заголовочные файлы

Как сделать заголовочный файл c. Смотреть фото Как сделать заголовочный файл c. Смотреть картинку Как сделать заголовочный файл c. Картинка про Как сделать заголовочный файл c. Фото Как сделать заголовочный файл c

Все мы при написании кода пользуемся правилами оформления кода. Иногда изобретаются свои правила, в других случаях используются готовые стайлгайды. Хотя все C++ программисты читают на английском легче, чем на родном, приятнее иметь руководство на последнем.
Эта статья является переводом части руководства Google по стилю в C++ на русский язык.
Исходная статья (fork на github), обновляемый перевод.

Заголовочные файлы

Желательно, чтобы каждый .cc файл исходного кода имел парный .h заголовочный файл. Также есть известные исключения из этого правила, такие как юниттесты или небольшие .cc файлы, содержащие только функцию main().

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

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

Независимые заголовочные файлы

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

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

Предпочтительно размещать определения для шаблонов и inline-функций в одном файле с их декларациями. И эти определения должны быть включены (include) в каждый .cc файл, использующий их, иначе могут быть ошибки линковки на некоторых конфигурациях сборки. Если же декларации и определения находятся в разных файлах, включение одного должно подключать другой. Не выделяйте определения в отдельные заголовочные файлы (-inl.h). Раньше такая практика была очень популярна, сейчас это нежелательно.

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

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

Блокировка от повторного включения

Для гарантии уникальности, используйте компоненты полного пути к файлу в дереве проекта. Например, файл foo/src/bar/baz.h в проекте foo может иметь следующую блокировку:

Предварительное объявление

По возможности, не используйте предварительное объявление. Вместо этого делайте #include необходимых заголовочных файлов.

Определение
«Предварительное объявление» — декларация класса, функции, шаблона без соответствующего определения.

Встраиваемые (inline) функции

Определяйте функции как встраиваемые только когда они маленькие, например не более 10 строк.

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

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

Чрезмерное использование встраиваемых функций может сделать программу медленнее. Также встраиваемые функции, в зависимости от размера её, могут как увеличить, так и уменьшить размер кода. Если это маленькие функции, то код может быть уменьшен. Если же функция большая, то размер кода может очень сильно вырасти. Учтите, что на современных процессорах более компактный код выполняется быстрее благодаря лучшему использованию кэша инструкций.

Хорошим правилом будет не делать функции встраиваемыми, если они превышают 10 строк кода. Избегайте делать встраиваемыми деструкторы, т.к. они неявно могут содержать много дополнительного кода: вызовы деструкторов переменных и базовых классов!

Ещё одно хорошее правило: обычно нет смысла делать встраиваемыми функции, в которых есть циклы или операции switch (кроме вырожденных случаев, когда цикл или другие операторы никогда не выполняются).

Важно понимать, что встраиваемая функция не обязательно будет скомпилирована в код именно так. Например, обычно виртуальные и рекурсивные функции компилируются со стандартным вызовом. Вообще, рекурсивные функции не должны объявляться встраиваемыми. Основная же причина делать встраиваемые виртуальные функции — разместить определение (код) в самом определении класса (для документирования поведения или удобства чтения) — часто используется для get/set функций.

Имена и Порядок включения (include)

Вставляйте заголовочные файлы в следующем порядке: парный файл (например, foo.h — foo.cc), системные файлы C, стандартная библиотека C++, другие библиотеки, файлы вашего проекта.

Все заголовочные файлы проекта должны указываться относительно директории исходных файлов проекта без использования таких UNIX псевдонимов как . (текущая директория) или .. (родительская директория). Например, google-awesome-project/src/base/logging.h должен включаться так:

Другой пример: если основная функция файлов dir/foo.cc иdir/foo_test.cc это реализация и тестирование кода, объявленного в dir2/foo2.h, то записывайте заголовочные файлы в следующем порядке:

Такой порядок файлов позволяет выявить ошибки, когда в парном заголовочном файле (dir2/foo2.h) пропущены необходимые заголовочные файлы (системные и др.) и сборка соответствующих файлов dir/foo.cc или dir/foo_test.cc завершится ошибкой. Как результат, ошибка сразу же появится у разработчика, работающего с этими файлами (а не у другой команды, которая только использует внешнюю библиотеку).

Обычно парные файлы dir/foo.cc и dir2/foo2.h находятся в одной директории (например, base/basictypes_test.cc и base/basictypes.h), хотя это не обязательно.

Учтите, что заголовочные файлы C, такие как stddef.h обычно взаимозаменяемы соответствующими файлами C++ (cstddef). Можно использовать любой вариант, но лучше следовать стилю существующего кода.

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

Следует включать все заголовочные файлы, которые объявляют требуемые вам типы, за исключением случаев предварительного объявления. Если ваш код использует типы из bar.h, не полагайтесь на то, что другой файл foo.h включает bar.h и вы можете ограничиться включением только foo.h: включайте явно bar.h (кроме случаев, когда явно указано (возможно, в документации), что foo.h также выдаст вам типы из bar.h).

Например, список заголовочных файлов в google-awesome-project/src/foo/internal/fooserver.cc может выглядеть так:

Бывают случаи, когда требуется включение заголовочных файлов в зависимости от условий препроцессора (например, в зависимости от используемой ОС). Такое включение старайтесь делать как можно короче (локализованно) и размещать после других заголовочных файлов. Например:

Источник

Урок №21. Заголовочные файлы

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

Заголовочные файлы из Стандартной библиотеки C++

Рассмотрим следующую программу:

Результат выполнения программы:

Как правило, в заголовочных файлах записываются только объявления, без определений. Следовательно, если cout только объявлен в заголовочном файле iostream, то где же он определяется? Ответ: в Стандартной библиотеке С++, которая автоматически подключается к вашему проекту на этапе линкинга.

Как сделать заголовочный файл c. Смотреть фото Как сделать заголовочный файл c. Смотреть картинку Как сделать заголовочный файл c. Картинка про Как сделать заголовочный файл c. Фото Как сделать заголовочный файл c

Пишем свои собственные заголовочные файлы

Теперь давайте вернемся к примеру, который мы обсуждали на предыдущем уроке. У нас было два файла: add.cpp и main.cpp.

Примечание: Если вы создаете все файлы заново, то не забудьте добавить add.cpp в свой проект, чтобы он был подключен к компиляции.

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

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

Написать свой собственный заголовочный файл не так уж и сложно. Заголовочные файлы состоят из двух частей:

Директивы препроцессора — в частности, header guards, которые предотвращают вызов заголовочного файла больше одного раза из одного и того же файла (об этом детально на следующем уроке).

Содержимое заголовочного файла — набор объявлений.

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

main.cpp, в котором мы подключаем add.h:

add.cpp остается без изменений:

Если вы получили ошибку от компилятора, что add.h не найден, то убедитесь, что имя вашего файла точно «add.h». Вполне возможно, что вы могли сделать опечатку, например, просто «add» (без «.h») или «add.h.txt» или «add.hpp».

Если вы получили ошибку от линкера, что функция аdd() не определена, то убедитесь, что вы корректно подключили add.cpp к вашему проекту (и к компиляции тоже)!

Угловые скобки (<>) vs. Двойные кавычки («»)

Вы, наверное, хотите узнать, почему используются угловые скобки для iostream и двойные кавычки для add.h. Дело в том, что, используя угловые скобки, мы сообщаем компилятору, что подключаемый заголовочный файл написан не нами (он является «системным», т.е. предоставляется Стандартной библиотекой С++), так что искать этот заголовочный файл следует в системных директориях. Двойные кавычки сообщают компилятору, что мы подключаем наш собственный заголовочный файл, который мы написали самостоятельно, поэтому искать его следует в текущей директории нашего проекта. Если файла там не окажется, то компилятор начнет проверять другие пути, в том числе и системные директории.

Правило: Используйте угловые скобки для подключения «системных» заголовочных файлов и двойные кавычки для ваших заголовочных файлов.

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

Еще один часто задаваемый вопрос: «Почему iostream (или любой другой из стандартных заголовочных файлов) при подключении пишется без окончания «.h»?». Дело в том, что есть 2 отдельных файла: iostream.h (заголовочный файл) и просто iostream! Для объяснения потребуется краткий экскурс в историю.

Кроме того, многие библиотеки, унаследованные от языка Cи, которые до сих пор используются в C++, также были продублированы с добавлением префикса c (например, stdlib.h стал cstdlib). Функционал этих библиотек также перенесли в пространство имен std, чтобы избежать возможность возникновения конфликтов имен с пользовательскими идентификаторами.

Правило: При подключении заголовочных файлов из Стандартной библиотеки С++, используйте версию без «.h» (если она существует). Пользовательские заголовочные файлы должны иметь окончание «.h».

Можно ли записывать определения в заголовочных файлах?

C++ не будет жаловаться, если вы это сделаете, но так делать не принято.

Как уже было сказано выше, при подключении заголовочного файла, всё его содержимое вставляется сразу же после строки с #include. Это означает, что любые определения, которые есть в заголовочном файле, скопируются в ваш файл.

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

Советы

Вот несколько советов по написанию собственных заголовочных файлов:

Всегда используйте директивы препроцессора.

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

Не определяйте функции в заголовочных файлах.

Каждый заголовочный файл должен выполнять свое конкретное задание и быть как можно более независимым. Например, вы можете поместить все ваши объявления, связанные с файлом А.cpp в файл A.h, а все ваши объявления, связанные с B.cpp — в файл B.h. Таким образом, если вы будете работать только с А.cpp, то вам будет достаточно подключить только A.h и наоборот.

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

Не подключайте одни заголовочные файлы из других заголовочных файлов.

Поделиться в социальных сетях:

Источник

Компилирование заголовочных файлов или документация на халяву

Для кого эта статья

Введение

Программирование на языке C++ — это в первую очередь анализ существующего кода.
Вот уже несколько лет я принимаю участие в разработке системы имитационного моделирования. Разработка ведется на великом и могучем… детище Страуструпа. И, надо сказать, сей проект уже давно пишется не столько на C++, сколько на собственном диалекте из макросов (ниже станет понятно, почему это важно). Думаю ситуация такая знакома многим C++ девелоперам.

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

Предыстория

Время шло, мой профессионализм (как я надеюсь) рос, проект развивался и захотелось мне навести порядок в библиотеке, которую активно правил. Под порядком здесь подразумевается концепция один заголовочный файл = одна сущность (или несколько небольших и тесно связанных), а также разделение всех файлов на три типа — заголовочные, внутренние и исходными — соответственно, *.h, *.inl, *.cpp. Причем разделение должно пройти таким образом, чтобы в заголовочных файлах не осталось определения ни одной функции-члена класса, а все они были либо в cpp-файлах, либо в inl-файлах. Помимо этого весь код модуля должен был быть отформатирован по единому стандарту, и, что самое интересное, большинство сущностей должны были быть откомментированны с использованием специальных команд doxygen’а (\interface, \class, \todo, \brief, \enum и прочие).

Проблема

Сказано — сделано. Спустя примерно несколько недель убитых вечеров безумная задача выполнена! Код красив так, что чуть слеза не наворачивается.
И настало время откинуться на спинку стула в ожидании, пока doxygen построит мне красивейшую и правильнейшую документацию с описанием моего (на тот момент уже самого любимого) модуля системы. И вот с замиранием сердца
cd Project/doc
doxygen project-doxyfile
cd html/
./index.html
Однако то, что предстало моему взору было, мягко говоря, фигней. Doxygen откровенно схалтурил: диаграммы неполные, namespace’ыпустые, макросы мне пытается за функции выдать, в общем всего не перечесть… Но первичный анализ показал, что он не понял макросы (все namespace’ы, умные указатели (собственного производства, кстати) и многое другое было задано с помощью макросов).

Решение в опции PREDEFINED?

Под подозрение в первую очередь попали настройки doxygen’а. Были перепроверены такие опции как ENABLE_PREPROCESSING, MACRO_EXPANSION, EXPAND_ONLY_PREDEF, SEARCH_INCLUDES и INCLUDE_PATH. Но настройки выглядели логично, а макросы правильно восприниматься не стали. Тогда в ход пошла опция PREDEFINED, хотя она и не отвечала на вопрос “почему doxygen лажает?”, но позволила ему объяснить нужные макросы. Это и стало решением проблемы на некоторое время. Но тот факт, что нужно было постоянно дописывать в эту опцию все новые макросы, весьма удручал и заставил продолжить изучение doxygen’а.

Проблема в коде!

Долго думал я над поведением doxygen’а и гуглил. А однажды даже слил его себе по svn с праведной мыслью найти и пофиксить страшный баг, мешающий ему обрабатывать макросы 🙂

Но вовремя остановился я, ибо понял в чем отличие его от компилятора. Цели у них весьма разные (и у доксигена в некотором роде даже сложнее), потому что ему надо не только понять cpp-файлы, но проделать тоже самое и с заголовочными файлами, чтобы потом красиво показать это все пользователю, т.е. по-файлово. И отсюда вытекает кое-что интересное, о чем я раньше не думал: компилятор не интересуется заголовочными файлами, на вход ему поступает “единица трансляции”, или выход препроцессора с уже отработанными директивами #include. Позволю себе перефразировать это таким образом — компирятор имеет дело с заголовочными файлами лишь косвенно, так сказать, в контексте единицы трансляции. И компилирует его в этом самом контексте. Отсюда следует неутешительный вывод — заголовочный файл может быть неправильным сам по себе, но становиться правильным в контексте своего использования, и компилятор ничего не скажет вам об этом!

А вот с doxygen’ом это не проходит — заголовочный файл анализируется как самостоятельный, самодостаточный документ с исходным кодом. И если в нем не хватает объявлений используемых сущностей (которые появляются в контексте использования данного заголовочного файла), то doxygen будет ошибаться. И очень похоже, что именно эта болезнь постигла наш проект.

Так какая же ошибка, неуловимая для компилятора, затаилась в заголовочном файле? Это недостоющии #include директивы тех файлов, где были определены пресловутые макросы. Т.е. при компиляции cpp-файлов все определения попадали в текущую единицу трансляции каким-нибудь обходным путем, а не через “проблемный” заголовочный файл. После этого открытия в нашей команде прошло совещание, с главным вопросом на повестке дня “и что с этим делать, собственно говоря?”. Вопрос был решен в пользу того, что подобное поведение — отсутствие всех нужных инклудов — ошибка. Основной довод весьма прост — заголовочный файл потенциально может быть включен куда угодно, и значит должен быть настолько самодостаточен, чтобы скомпилироваться.

Решение — “компиляции заголовочных файлов”

Вот тут-то и стал вопрос о “компиляции заголовочных файлов”.
Смысл этого мероприятия — заставить компилятор проанализировать все заголовочные файлы без внесения их в контекст исходных (*.cpp) файлов, и сообщить об ошибках. И тогда, если их исправить, то у doxygen’а не должно остаться никаких отмазок, чтобы правильно построить документацию по проекту со всеми диаграммами и прочим.

Ну что ж, осталось исправить ошибки и пользоваться doxygen’ом.

И, в общем-то, еще, наверняка, можно кое-что улучшить, например, прикрутить сюда cmake, но основная мысль данного поста именно в необходимости “компиляции заголовочных файлов” для борьбы с, казалось бы, странным поведением doxygen’а. Так что на этом остановлюсь, пока не утомил последнего самого усидчивого читателя 😉

Вместо выводов

И собственно, почему пост называется “… или документация на халяву”.
Да просто в коде может не быть ни одной строчки doxygen’овского комментария, но все равно можно построить по нему документацию со всевозможными диаграммами, что может очень помочь при изучении нового проекта. А именно автоматическое построение документации (с удобной навигацией и диаграммами) должно было облегчить новым студентам (а все это происходит в техническом ВУЗе) изучение весьма большой и сложной системы и являлось первоначальной целью использования doxygen в нашем проекте.

Естественно, в комментариях жду конструктивную критику, исправления, дополнения моих в меру кривых команд и вопросы.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *