Подтвердить что ты не робот

Управление форвардными декларациями

Хорошо известно, что использование форвардных объявлений предпочтительнее использовать #includes в заголовочных файлах, но какой лучший способ управлять форвардными объявлениями?

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

Передовые декларации typedefs (например, struct SensorRecordId; typedef std::vector<SensorRecordId> SensorRecordIdList;) также немного дублируют несколько файлов заголовков.

Итак, я создал файл ProjectForwards.h, содержащий все мои передовые объявления, и включил его там, где это было необходимо. Поначалу это казалось хорошей идеей - гораздо меньшей избыточностью и намного более легким обслуживанием typedefs. Но теперь, в результате использования ProjectForwards.h так сильно, всякий раз, когда я добавляю в него новый класс, мне приходится восстанавливать мир, который замедляет развитие.

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

4b9b3361

Ответ 1

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

  • Сделайте все возможное, чтобы разделить ProjectForwards.h на несколько файлов, как вы предложили. Убедитесь, что каждая подсистема получает только декларации, которые действительно нужны. Если ничего другого, этот процесс заставит вас задуматься о связи между вашими подсистемами, и вы можете найти способы его уменьшить. Это все хорошие шаги, чтобы избежать чрезмерной компиляции.

  • Мимика <iosfwd>. Пусть каждый общий класс или модуль предоставляет свой собственный заголовок forward-include, который просто предоставляет имена классов и любые удобные typedef. Тогда вы можете # включить это везде. Да, вы повторите этот список много, но подумайте об этом так: никто не жалуется на #including <vector>, <string> и <map> в шести разных местах в своем коде.

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

Ответ 2

В общем:

  • Имейте файл вперед для пользователей вашего модуля. Это будет объявлять только те классы, которые отображаются как часть API.

  • Если вы обычно использовали форварды в своей реализации, вы можете иметь только форвардный файл, основанный на реализации.

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

Ответ 3

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

///Begin Forwarding
...
///End Forwarding

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

Ответ 4

Я никогда не видел "заголовка форвардных деклараций", который был действительно полезен (никто его не использует), не быстро стал устаревшим (полный вещей, который никто не использует), и не был узким местом в итерации (коснулся вперед объявить заголовок? перекомпилируйте все!). Как правило, они разрабатывают все три проблемы.

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

Ответ 5

Я не думаю, что есть одно "лучшее" решение, у каждого есть свои преимущества и недостатки. Несмотря на то, что это больше работает, я лично одобряю подход "каждый заголовочный файл имеет свой собственный подход вперед" по следующим причинам:

  • Он как бы тоньше, чем он может получить: никаких дополнительных файлов, которые необходимо найти и проанализировать.
  • Нет обфускации: просто посмотрев на заголовочный файл, вы точно увидите, какие типы ему нужны.
  • Отсутствие ненужного загрязнения пространства имен. Если вы собираете декларации в формате ProjectForwards.h, этот файл будет содержать сумму всех деклараций, необходимых всем ее потребителям. Поэтому, если только один потребитель нуждается в определенном объявлении, все остальные наследуют его тоже.

Если эти аргументы не убедительны, может быть, потому, что они слишком пуричны:-), тогда я бы предложил следующий способ разделения ProjectForwards.h.

Ответ 6

Вот что я обычно делаю:

Хорошо известно, что использование форвардных объявлений предпочтительнее использовать #includes в заголовочных файлах, но какой лучший способ управлять форвардными объявлениями?

  • Библиотека. Предоставьте выделенный клиент вперед: (например, #include "MONThread/include.fwd.hpp"). Храните библиотеки в фокусе (small-ish) и делайте реализаций приватными, где это возможно.

  • Исполняемый файл: Forward объявляет по требованию, если только он не приходит из библиотеки - всегда используйте библиотеку forward include. Признайте, какая должна быть библиотека (логическая или физическая) - многие форварды предлагают это, поскольку шаблоны появятся. Также попытайтесь изолировать то, что может быть скрыто в процессе. С библиотеками и исполняемыми файлами должно быть некоторое использование частных типов пакетов - эти типы не относятся к заголовкам клиента.

Итак, я создал файл ProjectForwards.h, содержащий все мои передовые декларации, и включил это там, где это было необходимо. Поначалу это казалось хорошей идеей - гораздо меньшей избыточностью и намного более легким обслуживанием typedefs. Но теперь, в результате использования ProjectForwards.h так сильно, всякий раз, когда я добавляю к нему новый класс, мне приходится восстанавливать мир, который замедляет развитие.

Обычно это означает, что слишком много больших библиотек видны на высоких уровнях графика include. Идеал включает граф (большой системы) намного шире, чем он высок - включая то, что ему нужно с минимальным избытком. Если для каждого TU требуется несколько 100 000 строк, вам не составит труда - начните удалять большие библиотеки с высоких уровней.

Если это действительно звучит неудовлетворительно, проанализируйте свои зависимости от программы.

  • Многие люди делают ошибку (в более крупных проектах) включением тонны больших библиотек для удобства (например, в pch), что приводит к перекомпиляции мира (и pch).

  • Время от времени оценивайте свои зависимости - устанавливайте некоторые мягкие разумные пределы для количества строк вывода препроцессора.

  • Передовые заголовки заменяют локальные объявления вперед. Они не входят (обычно) в pch.

Ответ 7

Лично я включаю только глобальные ProjectForwards.h объявления, которые являются действительно глобальными для всех или, в основном, для всех. Он также может включать в себя другие файлы, которые почти всегда необходимы, например:

#include <string>
#include <vector>
#include <boost/shared_ptr.hpp>

std::string get_installation_dir();
//...

Таким образом, этот файл редко изменяется, и нет необходимости часто перестраивать.

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

Ответ 8

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

Это единственный хороший способ.

Кроме того, если у вас есть typedef где-то, лучше как-то замаскировать его. Например, вместо использования typedef, например:

typedef std::vector< MyClass > MyClassArray;

сделайте это вместо:

struct MyClassArray
{
  std::vector< MyClass > t;
};

Плохо то, что вы не сможете использовать операторов, поэтому это не всегда будет работать. Например, если у вас есть

typedef std::string MyString;

тогда лучше пойти с typedef.

Итак, я создал файл ProjectForwards.h, содержащий все мои передовые объявления, и включил его там, где это было необходимо.

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

Ответ 9

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