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

Ваша предпочтительная политика заголовка C/С++ для больших проектов?

При работе над большим проектом C/С++ существуют ли у вас определенные правила относительно #include в исходных или заголовочных файлах?

Например, мы можем представить, как следовать одному из этих двух чрезмерных правил:

  • #include запрещены в .h файлах; это зависит от каждого файла .c, чтобы включить все необходимые ему заголовки.
  • Каждый файл .h должен содержать все его зависимости, то есть он должен иметь возможность самостоятельно компилировать без каких-либо ошибок.

Я предполагаю, что между любыми проектами есть компромисс, но что твой? У вас есть более конкретные правила? Или любая ссылка, которая утверждает, что для любого из решений?

4b9b3361

Ответ 1

Выполнение .h включает только в файлы C означает, что если я просто включу заголовок (определяющий что-то, что я хочу использовать в файле C), возможно, не удастся. Это может закончиться неудачей, потому что я должен включить 20 других заголовков. И что еще хуже, я должен включить их в правильный порядок. С большим количеством файлов .h, эта система заканчивается, чтобы быть адским ад в долгосрочной перспективе. Вы просто хотите включить один файл .h в один файл .c, и вы потратите 2 часа, чтобы узнать, какие другие .h файлы вам нужны, и в каком порядке вы должны их включить.

Если файлу .h требуется другой файл .h, который должен быть успешно включен в файл C, который не содержит ничего, кроме этого .h файла и не вызывая ошибок компиляции, я включу этот другой .h файл в .h сам файл. Таким образом, я уверен, что каждый файл C может включать в себя каждый файл .h, и он никогда не вызовет ошибок. Файл .c никогда не должен беспокоиться о том, какие другие .h файлы импортировать или в каком порядке их включить. Это работает даже с огромными проектами (1000.h файлов и выше).

С другой стороны, я никогда не включаю файл .h в другой, если это необязательно. Например. если у меня hashtable.h и hashtable.c, и hashtable.c нуждается в hashing.h, но hashtable.h не нуждается в этом, я не включаю его там. Я включаю его только в файл .c, так как другие файлы .c, включая hashtable.h, также не нуждаются в hashing.h, и если они им нужны по какой-либо причине, они должны включать его.

Ответ 2

Я думаю, что оба предложенных правила плохие. В моей части я всегда применяю:

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

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

Ответ 3

Я бы использовал правило 2:

Все заголовки должны быть самодостаточными, будь то:

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

Таким образом, если у вас есть пустой исходный файл C/С++, в том числе заголовок должен правильно компилироваться.

Затем в исходном файле C/С++ укажите только то, что необходимо: если HeaderA forward-объявил символ, определенный в HeaderB, и что вы используете этот символ, вам придется включить оба... Хорошая новость: что если вы не используете форвард-объявленный символ, тогда вы сможете включить только HeaderA и избегать включения HeaderB.

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

Ответ 4

Первое правило завершится с ошибкой, как только появятся круговые зависимости. Поэтому его нельзя применять строго.

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

Я все сторонник правила 2 (хотя, возможно, было бы неплохо включить "заголовки прямых объявлений" вместо реальной сделки, как в <iosfwd>, потому что это сокращает время компиляции). Вообще-то, я считаю, что это своего рода самодокументация, если заголовочный файл "объявляет", какие у него есть зависимости, и какой лучший способ сделать это, чем включать необходимые файлы?

EDIT:

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

Это не правильно. Фактически, круговые зависимости между классами могут быть неизбежными и вовсе не являются признаком плохого дизайна. Примеры многочисленны, позвольте мне просто упомянуть шаблон Observer, который имеет круговую ссылку между наблюдателем и субъектом.

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

// observer.hpp

class Observer; // Forward declaration.

#ifndef MYLIB_OBSERVER_HPP
#define MYLIB_OBSERVER_HPP

#include "subject.hpp"

struct Observer {
    virtual ~Observer() = 0;
    virtual void Update(Subject* subject) = 0;
};

#endif

// subject.hpp
#include <list>

struct Subject; // Forward declaration.

#ifndef MYLIB_SUBJECT_HPP
#define MYLIB_SUBJECT_HPP

#include "observer.hpp"

struct Subject {
    virtual ~Subject() = 0;
    void Attach(Observer* observer);
    void Detach(Observer* observer);
    void Notify();

private:
    std::list<Observer*> m_Observers;
};

#endif

Ответ 5

Минимальная версия 2..h файлов включает только файлы заголовков, которые она специально требует для компиляции, с использованием прямого объявления и pimpl, насколько это практически возможно.

Ответ 6

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

Ответ 7

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

Ответ 8

Первая альтернатива (нет #include в заголовках) является для меня немой. Я хочу свободно #include независимо от того, что мне может понадобиться, не беспокоясь о ручном #include с его зависимостями. Итак, в общем, я следую второму правилу.

Что касается циклических зависимостей, мое личное решение состоит в том, чтобы структурировать мои проекты в терминах модулей, а не в классах. Внутри модуля все типы и функции могут иметь произвольные зависимости друг от друга. На всех границах модулей между модулями могут не быть круговых зависимостей. Для каждого модуля существует один файл *.hpp и один файл *.cpp. Это гарантирует, что любые форвардные объявления (необходимые для круговых зависимостей, которые могут произойти только внутри модуля) в заголовке, в конечном счете, всегда разрешаются внутри одного и того же заголовка. Нет необходимости в заголовках только для прямой декларации.

Ответ 9

Pt. 1 терпит неудачу, если вы хотите иметь предварительно скомпилированные заголовки через определенный заголовок; например. это то, что StdAfx.h для VisualStudio: вы помещаете туда все общие заголовки...

Ответ 10

Это сводится к дизайну интерфейса:

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

В Windows мой stdafx только когда-либо включает в себя afx ___. h заголовки - нет библиотек строк, векторов или ускорений.

Ответ 11

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

Трюк заключается в том, что, как уже отмечалось несколькими другими, используют как можно более сложные объявления, т.е. если используются ссылки или указатели. Для минимизации зависимостей построения зависимостей таким образом может быть полезно pimpl.

Ответ 12

Я согласен с Меккой, короче говоря,

для каждого foo.h в вашем проекте включают только те заголовки, которые необходимы для создания

// foo.c
#include "any header"
// end of foo.c

компиляции.

(При использовании предварительно скомпилированных заголовков они, конечно, разрешены, например, #include "stdafx.h" в MSVC)

Ответ 13

Лично я делаю так:
1 Perfer forward объявляет о включении других файлов .h в файл .h. Если что-то может быть использовано как указатель/ссылка в этих файлах или классах .h, объявление вперед возможно без ошибки компиляции. Это может сделать заголовки менее включенными зависимостями (сохранить время компиляции? Не уверен:().
2 Сделайте .h файлы простыми или конкретными. например плохо определить все константы в файле CONST.h, лучше делить их на несколько таких, как CONST_NETWORK.h, CONST_DB.h. Поэтому, чтобы использовать одну константу БД, ей не нужно включать другую информацию о сети.
3 Не помещайте реализацию в заголовки. Заголовки используются для быстрого обзора публичных вещей для других людей; при их реализации не загрязняйте декларацию деталями для других.