Я очищаю включения в проекте C ++, над которым я работаю, и все время задаюсь вопросом, следует ли мне явно включать все заголовки, используемые непосредственно в конкретном файле, или же я должен включать только минимум.
Вот пример Entity.hpp
:
#include "RenderObject.hpp"
#include "Texture.hpp"
struct Entity {
Texture texture;
RenderObject render();
}
(Предположим, что предварительное объявление для RenderObject
не вариант.)
Теперь я знаю, что это RenderObject.hpp
включает Texture.hpp
- я знаю это, потому что у каждого RenderObject
есть Texture
член. Тем не менее, я явно включаю Texture.hpp
в Entity.hpp
, потому что я не уверен, если это хорошая идея, чтобы полагаться на то, что он включен в RenderObject.hpp
.
Итак: это хорошая практика или нет?
#ifndef _RENDER_H #define _RENDER_H ... #endif
.#pragma once
решает вопрос, нет?Ответы:
Вы должны всегда включать все заголовки, определяющие любые объекты, используемые в файле .cpp в этом файле, независимо от того, что вы знаете о том, что находится в этих файлах. Вы должны включить защиту во все заголовочные файлы, чтобы убедиться, что включение заголовков несколько раз не имеет значения.
Причины:
Texture
объектами в этом файле.RenderObject.hpp
самом деле не нуждается вTexture.hpp
себе.Следствие состоит в том, что вы никогда не должны включать заголовок в другой заголовок, если он явно не нужен в этом файле.
источник
Общее правило: включите то, что вы используете. Если вы используете объект напрямую, то включите его заголовочный файл напрямую. Если вы используете объект A, который использует B, но не используете B самостоятельно, включите только Ah
Кроме того, пока мы обсуждаем эту тему, вы должны включать в заголовочный файл другие файлы заголовков, только если они вам действительно нужны. Если он вам нужен только в .cpp, то включите его только там: это разница между публичной и частной зависимостями, и он не позволит пользователям вашего класса перетаскивать заголовки, которые им на самом деле не нужны.
источник
Да.
Вы никогда не знаете, когда эти другие заголовки могут измениться. В этом мире имеет смысл включать в каждую единицу перевода заголовки, которые, как вы знаете, нужны этой единице перевода.
У нас есть охрана заголовков, чтобы гарантировать, что двойное включение не вредно.
источник
Мнения расходятся по этому вопросу, но я считаю, что каждый файл (будь то исходный файл c / cpp или заголовочный файл h / hpp) должен иметь возможность компилироваться или анализироваться самостоятельно.
Таким образом, все файлы должны #include любые и все заголовочные файлы, которые им нужны - вы не должны предполагать, что один заголовочный файл уже был включен ранее.
Это реальная боль, если вам нужно добавить заголовочный файл и обнаружить, что он использует элемент, который определен в другом месте, без непосредственного его включения ... поэтому вам нужно найти (и, возможно, в итоге ошибиться!)
С другой стороны, не имеет значения (как правило), если вы #include файл, который вам не нужен ...
В качестве личного стиля я размещаю файлы #include в алфавитном порядке, разбивая их на системы и приложения - это помогает усилить «самодостаточное и полностью связное» сообщение.
источник
Это зависит от того, является ли это транзитивное включение необходимостью (например, базовым классом) или из-за деталей реализации (закрытый член).
Чтобы уточнить, транзитивное включение необходимо при удалении, оно может быть сделано только после первого изменения интерфейсов, объявленных в промежуточном заголовке. Поскольку это уже серьезное изменение, любой файл .cpp, использующий его, должен быть проверен в любом случае.
Пример: Ah включен в Bh, который используется C.cpp. Если Bh использовал Ah для некоторых деталей реализации, то C.cpp не должен предполагать, что Bh продолжит делать это. Но если Bh использует Ah для базового класса, то C.cpp может предположить, что Bh продолжит включать соответствующие заголовки для своих базовых классов.
Вы видите здесь фактическое преимущество НЕ дублирования включений заголовка. Скажем, что базовый класс, используемый Bh, на самом деле не принадлежит классу Ah и преобразован в сам Bh. Bh теперь автономный заголовок. Если C.cpp избыточно включил Ah, теперь он включает ненужный заголовок.
источник
Может быть другой случай: у вас есть Ah, Bh и ваш C.cpp, Bh включает Ah
так что в C.cpp вы можете написать
Так что, если вы не напишите здесь #include «Ах», что может произойти? в вашем C.cpp используются A и B (например, класс). Позже вы изменили свой код C.cpp, удалили вещи, связанные с B, но оставили Bh включенным там.
Если вы включите и Ah, и Bh, а теперь на этом этапе, инструменты, которые обнаруживают ненужные включения, могут помочь вам указать, что включение Bh больше не требуется. Если вы включаете только Bh, как указано выше, то инструментам / людям трудно обнаружить ненужные включения после изменения кода.
источник
Я придерживаюсь слегка отличающегося подхода от предложенных ответов.
В заголовках всегда включайте только минимум, то, что нужно для прохождения компиляции. Используйте предварительное объявление везде, где это возможно.
В исходных файлах не так важно, сколько вы включаете. Мои предпочтения по-прежнему включают минимум, чтобы он прошел.
Для небольших проектов, в том числе заголовков, тут и там не будет никакого значения. Но для средних и крупных проектов это может стать проблемой. Даже если для компиляции используется новейшее оборудование, разница может быть заметной. Причина в том, что компилятор все еще должен открыть включенный заголовок и проанализировать его. Итак, чтобы оптимизировать сборку, примените описанную выше технику (включите минимум и используйте forward forward).
Несмотря на то, что дизайн программного обеспечения Large Scale C ++ (от Джона Лакоса) несколько устарел, все это подробно объясняется.
источник
Хорошей практикой является не беспокоиться о вашей стратегии заголовка, пока она компилируется.
Раздел заголовка вашего кода - это просто блок строк, на который никто даже не должен смотреть, пока вы не получите легко решаемую ошибку компиляции. Я понимаю стремление к «правильному» стилю, но ни один из способов не может быть описан как правильный. Включение заголовка для каждого класса с большей вероятностью вызовет досадные ошибки компиляции на основе порядка, но эти ошибки компиляции также отражают проблемы, которые может исправить осторожное кодирование (хотя, возможно, они не стоят времени на исправление).
И да, у вас будут проблемы, связанные с порядком, как только вы начнете попадать на
friend
землю.Вы можете думать о проблеме в двух случаях.
Случай 1: у вас есть небольшое количество классов, взаимодействующих друг с другом, скажем, менее дюжины. Вы регулярно добавляете, удаляете и иным образом изменяете эти заголовки так, чтобы это могло повлиять на их зависимости друг от друга. Это тот случай, который предлагает ваш пример кода.
Набор заголовков достаточно мал, чтобы решить любые возникающие проблемы. Любые сложные проблемы решаются путем переписывания одного или двух заголовков. Беспокойство по поводу вашей стратегии заголовка - это решение проблем, которых не существует.
Случай 2: у вас есть десятки классов. Некоторые из классов представляют основу вашей программы, и перезапись их заголовков заставит вас переписать / перекомпилировать большое количество вашей кодовой базы. Другие классы используют эту основу для выполнения задач. Это представляет собой типичный бизнес-обстановку. Заголовки распределены по каталогам, и вы не можете реально вспомнить имена всех.
Решение: на этом этапе вам нужно думать о своих классах в логических группах и собирать эти группы в заголовки, которые мешают вам
#include
снова и снова. Это не только упрощает жизнь, но и является необходимым шагом для использования преимуществ предварительно скомпилированных заголовков .Вы заканчиваете
#include
занятия, которые вам не нужны, но кого это волнует ?В этом случае ваш код будет выглядеть так ...
источник