Управление предварительными декларациями - PullRequest
29 голосов
/ 07 февраля 2012

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

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

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

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

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

Ответы [ 9 ]

7 голосов
/ 17 февраля 2012

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

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

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

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

6 голосов
/ 07 февраля 2012

В общем:

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

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

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

3 голосов
/ 18 февраля 2012

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

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

3 голосов
/ 18 февраля 2012

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

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

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

2 голосов
/ 18 февраля 2012

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

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

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

1 голос
/ 19 февраля 2012

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

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

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

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

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

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

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

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

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

  • Заголовки forward заменяют локальные декларации forward.Они не (как правило) принадлежат в пч.

0 голосов
/ 22 февраля 2012

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

0 голосов
/ 21 февраля 2012

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

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

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

typedef std::vector< MyClass > MyClassArray;

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

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

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

typedef std::string MyString;

, то лучше использовать typedef.

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

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

0 голосов
/ 18 февраля 2012

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

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

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

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

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

...