Организация файлов заголовков классов C ++

Какие рекомендации по кодированию на C ++ и организации файлов вы предлагаете людям, которым приходится иметь дело с множеством взаимозависимых классов, распределенных по нескольким исходным файлам и файлам заголовков?

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


person Ashwin Nanjappa    schedule 06.12.2008    source источник


Ответы (6)


Некоторые общие рекомендации:

  • Соедините свои интерфейсы с реализациями. Если у вас есть foo.cxx, все, что там определено, лучше объявить в foo.h.
  • Убедитесь, что каждый файл заголовка # включает все остальные необходимые заголовки или форвардные объявления, необходимые для независимой компиляции.
  • Не поддавайтесь искушению создать заголовок "все". Они всегда доставляют неприятности в будущем.
  • Поместите набор связанных (и взаимозависимых) функций в один файл. Java и другие среды поощряют использование одного класса для каждого файла. В C ++ часто требуется один набор классов для каждого файла. Это зависит от структуры вашего кода.
  • Prefer forward declaration over #includes whenever possible. This allows you to break the cyclic header dependencies. Essentially, for cyclical dependencies across separate files, you want a file-dependency graph that looks something like this:
    • A.cxx requires A.h and B.h
    • B.cxx требует A.h и B.h
    • A.h требуется B.h
    • B.h является независимым (и вперед объявляет классы, определенные в A.h)

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

  • При необходимости используйте понятие «приватных заголовков». То есть файлы заголовков, которые требуются нескольким исходным файлам, но никогда не требуются общедоступным интерфейсом. Это может быть файл с общими встроенными функциями, макросами или внутренними константами.
  • Отделите публичный интерфейс от частной реализации на уровне файловой системы. Я обычно использую подкаталоги include/ и src/ в своих проектах C или C ++, где include/ содержит все мои общедоступные заголовки, а src/ - все мои источники. и частные заголовки.

Я бы порекомендовал найти копию книги Джона Лакоса Разработка программного обеспечения на C ++ в больших масштабах. Это довольно объемная книга, но если вы просто пролистаете некоторые из его дискуссий о физической архитектуре, вы многому научитесь.

person Tom    schedule 06.12.2008
comment
Отличные баллы. Предложение: перефразируйте маркер 2, чтобы он сказал «предварительные объявления или #includes», чтобы подчеркнуть, что нет необходимости включать заголовок, если предварительного объявления достаточно. - person Jonathan Leffler; 06.12.2008
comment
Хороший пост. Чаще всего слежу за ним. Как раз на уровне файловой системы, во время разработки каждого модуля я храню файлы заголовков в той же директории. Каждый раз, когда модуль переходит в стабильную версию, заголовок вместе с двоичным файлом «устанавливается», чтобы остальные модули могли видеть в другом каталоге. - person David Rodríguez - dribeas; 06.12.2008
comment
Я отмечаю, что пункт 5 действительно подчеркивает то, что я предлагаю для пункта 2, но простое изменение порядка условий все равно было бы хорошо, IMNSHO. - person Jonathan Leffler; 06.12.2008
comment
хороший пост. но у меня были случаи, когда у меня были локальные служебные классы, определенные в области пространства имен в анонимном пространстве имен (скажем, классы функциональных объектов, перегружающие op ()), которые были определены в моем файле .cxx, не объявленном в файле .hpp. - person Johannes Schaub - litb; 06.12.2008
comment
Я искренне поддерживаю рекомендацию книги Джона Лако: все, кто работает над крупными проектами на C ++, должны прочитать эту книгу! - person Stephen C. Steel; 05.10.2009
comment
Не могли бы вы добавить некоторые подробности, почему заголовок "все" - плохая идея? Это кажется довольно распространенным шаблоном, особенно когда рассматриваемое программное обеспечение является общей библиотекой, а не приложением. - person j b; 05.08.2014

Ознакомьтесь со стандартами кодирования C и C ++ в Центре космических полетов имени Годдарда НАСА . Одно правило, которое я специально отметил в стандарте C и принятое в моем собственном коде, - это правило, обеспечивающее «автономный» характер файлов заголовков. В файле реализации xxx.cpp для заголовка xxx.h убедитесь, что xxx.h является первым включенным заголовком. Если заголовок не является самодостаточным в любое время, компиляция завершится ошибкой. Это прекрасное простое и эффективное правило.

Единственный раз, когда он терпит неудачу, это если вы переносите между машинами, а заголовок xxx.h включает, скажем, <pqr.h>, но <pqr.h> требует средств, которые оказываются доступными с помощью заголовка <abc.h> на исходной платформе (так что <pqr.h> включает <abc.h>), но возможности не предоставляются <abc.h> на другой платформе (вместо этого они находятся в def.h, но <pqr.h> не включает <def.h>). Это не ошибка правила, и проблема легче диагностируется и устраняется, если вы следуете правилу.

person Jonathan Leffler    schedule 06.12.2008
comment
'detect-os.h' может установить #define OS как win / linux и т. д., а #include может использовать препроцессор или автоматическое объединение строк для включения правильного файла. (ОС -file.h == ›win-file.h / linux-file.h - person vrdhn; 06.02.2009

Проверьте раздел файла заголовка в руководстве по стилю Google.

person yesraaj    schedule 06.12.2008

Ответ Тома отличный!

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

Логика этого хорошо описана в прекрасной книге «Ускоренный C ++» (Ссылка на Amazon - дезинфицирована для скрипта kiddie link нацистов)

person Rob Wells    schedule 06.12.2008
comment
@Rob, не могли бы вы сказать нам, в каком разделе Accelerated C ++ можно найти объяснение, которое вы упомянули? - person jwfearn; 06.12.2008
comment
@jwfearn в данный момент я не могу, так как моя копия временно хранится. Я постараюсь выяснить это для вас, хотя - person Rob Wells; 11.12.2008
comment
@jwfearn - мой экземпляр действительно работал, похороненный вместе с другими моими книгами. Раздел 4.3 - это то, что вам нужно, а именно абзац примерно на полпути вниз на P67. HTH. ура, Роб - person Rob Wells; 15.12.2008

Еще один момент в дополнение к другим здесь:

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

Кажется очевидным, но я вижу это часто.

person Steve Fallows    schedule 07.12.2008

Я хотел бы добавить одну очень хорошую практику (как на C, так и на C ++), которую часто забывают:

foo.c

#include "foo.h" // always the first directive

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

Если в какой-то момент вам нужно что-то добавить перед включением этого заголовка (кроме, конечно, комментариев), то, скорее всего, вы делаете что-то не так. Если вы действительно не знаете, что делаете ... что приводит к другому более важному правилу => комментируйте свои хаки!

person Alex    schedule 30.06.2013