Форвардная декларация против включения

17

Reduce the number of #include files in header files. It will reduce build times. Instead, put include files in source code files and use forward declarations in header files.

Я прочитал это здесь. http://www.yolinux.com/TUTORIALS/LinuxTutorialC++CodingStyle.html .

Таким образом, он говорит, что если класс (класс A) в заголовочном файле не должен использовать фактическое определение некоторого класса (класс B). В то время мы можем использовать предварительное объявление вместо включения конкретного (класса B) заголовочного файла.

Вопрос: Если класс (класс A) в заголовке не использует фактическое определение определенного класса (класс B), то как предварительное объявление помогает сократить время компиляции?

Наяна Адассурия
источник

Ответы:

11

Компилятору не важно, использует ли класс A класс B. Он знает только, что, когда класс A компилируется и у него нет предварительного объявления класса B (прямое объявление или иное), он паникует и помечает его как ошибку.

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

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

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

Нил
источник
Спасибо за объяснение. Тогда нормально , как , например , вы думаете , есть три файла заголовок vehicle.h, bus.h, toybus.h. vehicle.hвключать bus.hи bus.hвключать toybus.h. так что если я сделаю некоторые изменения в bus.h. компилятор открывает и vehicle.hснова разбирает ? это компилирует это снова?
Наяна Адассурия
1
@NayanaAdassuriya Да, он включается и анализируется каждый раз, поэтому вы также видите #pragma onceили #ifndef __VEHICLE_H_вводите объявления в заголовочных файлах, чтобы такие файлы не включались многократно (или использовались многократно, по крайней мере, в случае ifndef).
Нил
4

потому что тогда A.hpp не нужно включать

поэтому A.hpp становится

class B;//or however forward decl works for classes

class A
{
    B* bInstance_;
//...
}

поэтому, когда A.hpp включен, B.hpp не включается неявно, и все файлы, которые зависят только от A.hpp, не нужно перекомпилировать каждый раз, когда изменяется b.hpp.

чокнутый урод
источник
но в исходном файле (A.cpp). необходимо включить фактический заголовочный файл (Bh). Так что каждый раз его нужно компилировать. Наконец, оба способа Bh нужно перекомпилировать с изменениями. Любой другой?
Наяна Адассурия
@NayanaAdassuriya нет, потому что A использует только указатель на B, а изменение на B не повлияет на A.hpp (или файлы, которые его содержат)
ratchet freak
@NayanaAdassuriya: Да, A.cpp придется перекомпилировать (если он использует определение B внутри тел методов A, но обычно это делает), но C.cpp, который использует A, но не B напрямую, не будет.
Ян Худек
3

Помните, препроцессор C / C ++ - это отдельный, чисто текстовый этап обработки. В #includeдирективе вытягивает содержание включенного заголовка и компилятор должен разобрать его. Более того, компиляция каждого из них .cppявляется совершенно отдельной, поэтому тот факт, что компилятор просто анализирует B.hпри компиляции B.cpp, не помогает ему в меньшей степени, когда он снова нужен при компиляции A.cpp. И снова при компиляции C.cpp. И D.cpp. И так далее. И каждый из этих файлов должен быть перекомпилирован, если какой-либо включенный в него файл изменился.

Так, скажем, класс Aиспользует класс Bи классы Cи Dиспользует класс A, но не нуждается в манипулировании B. Если класс Aможет быть объявлен просто с помощью forward-декларации of B, то B.hон компилируется дважды: при компиляции B.cppи A.cpp(потому что Bвсе еще требуется внутри Aметодов).

Но когда A.hвключает в себя B.h, он компилируется в четыре раза, при компиляции B.cpp, A.cpp, C.cppи , D.cppкак позже два ныне косвенно включает в себя B.hтакже.

Также, когда заголовок включен более одного раза, препроцессор все равно должен читать его каждый раз. Он пропустит обработку своего содержимого из-за защиты #ifdefs, но он все еще читает ее и должен искать конец защиты, что означает, что он должен проанализировать все директивы препроцессора внутри.

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

Ян Худек
источник
+1, заголовочные включения становятся серьезной проблемой, когда у вас довольно большое количество классов, а не только когда есть два класса А и В. Кажется, что все остальные посты упускают эту центральную точку.
Док Браун
2

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

Кроме того, если вы измените что-либо в файле заголовка для класса B, все, что включает этот заголовок, придется перекомпилировать. С предварительным объявлением, это может быть только исходный файл, в котором находится реализация A. Но если заголовок A фактически включает заголовок B, все, включая включая, a.hppбудет также перекомпилировано, даже если он не использует ничего из B.

Бенджамин Клостер
источник