Организация файлов заголовков классов С++
Каковы рекомендации по кодированию и каталогу на С++, которые вы предлагаете для людей, которым приходится иметь дело с большим количеством взаимозависимых классов, распространяемых по нескольким исходным и заголовочным файлам?
У меня есть эта ситуация в моем проекте и решение связанных с определением классов ошибок, пересекающих несколько файлов заголовков, стало довольно головной болью.
Ответы
Ответ 1
Некоторые общие рекомендации:
- Соедините свои интерфейсы с реализациями. Если у вас есть
foo.cxx
, все, что определено в нем, было лучше объявлено в foo.h
.
- Убедитесь, что каждый заголовочный файл # включает все остальные необходимые заголовки или форвардные объявления, необходимые для независимой компиляции.
- Сопротивляйтесь соблазну создать заголовок "все". Они всегда беспокоят дорогу.
- Поместите набор связанных (и взаимозависимых) функций в один файл. Java и другие среды поощряют использование одного класса для каждого файла. С С++ вам часто нужен один набор классов для каждого файла. Это зависит от структуры вашего кода.
- Предпочитайте форвардную декларацию над
#include
, когда это возможно. Это позволяет разбить циклические зависимости заголовка. По существу, для циклических зависимостей между отдельными файлами вы хотите, чтобы график зависимости от файлов выглядел примерно так:
-
A.cxx
требуется A.h
и B.h
-
B.cxx
требуется A.h
и B.h
-
A.h
требуется B.h
-
B.h
является независимым (и forward-declares классы, определенные в A.h
)
Если ваш код предназначен для библиотеки, потребляемой другими разработчиками, необходимо предпринять следующие дополнительные шаги:
- При необходимости используйте понятие "частные заголовки". То есть заголовочные файлы, требуемые несколькими исходными файлами, но никогда не требуемые публичным интерфейсом. Это может быть файл с общими встроенными функциями, макросами или внутренними константами.
- Отделите свой публичный интерфейс от частной реализации на уровне файловой системы. Я обычно использую подкаталоги
include/
и src/
в моих проектах на C или С++, где include/
имеет все мои публичные заголовки, а src/
- все мои источники. и частные заголовки.
Я бы рекомендовал найти копию книги Джона Лакоса "Крупномасштабная разработка программного обеспечения на C++". Это довольно внушительная книга, но если вы просто просмотрите некоторые из своих обсуждений по физической архитектуре, вы узнаете много.
Ответ 2
Ознакомьтесь со стандартами кодирования C и С++ в Центре космических полетов NASA Годдард. Единственное правило, которое я специально отметил в стандарте 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>
). Это не является ошибкой правила, и проблема легче диагностируется и исправляется, если вы следуете правилу.
Ответ 3
Проверьте раздел файла заголовка в Руководство по стилю Google
Ответ 4
Ответ Tom - отличный!
Единственное, что я хотел бы добавить, - никогда не "использовать объявления" в файлах заголовков. Они должны разрешаться только в файлах реализации, например. foo.cpp
.
Логика для этого хорошо описана в превосходной книге "Ускоренный С++" (ссылка Amazon - дезинфицирована для script kiddie link nazis )
Ответ 5
Еще один момент в дополнение к остальным здесь:
Не включайте никаких частных определений в включенном файле. Например. Любые определение, которое используется только в xxx.cpp должен находиться в xxx.cpp, а не xxx.h.
Кажется очевидным, но я вижу это
часто.
Ответ 6
Я бы хотел добавить одну очень хорошую практику (как на C и С++), которая часто оставляется:
foo.c
#include "foo.h" // always the first directive
Любые другие необходимые заголовки должны следовать, а затем код. Дело в том, что вы почти всегда нуждаетесь в этом заголовке для этого блока компиляции в любом случае и в том числе в качестве первой директивы, гарантируя, что заголовок остается самодостаточным (если это не так, будут ошибки). Это особенно актуально для публичных заголовков.
Если в любой момент вам нужно поставить что-то перед включением заголовка (за исключением комментариев, конечно), то, скорее всего, вы делаете что-то неправильно. Если вы действительно не знаете, что делаете... что приводит к еще одному решающему правилу = > комментируйте свои хаки!