Форвард декларації проти включати


18

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), то як подання декларації допомагає скоротити час компіляції?

Відповіді:


12

Компілятору не важливо, якщо клас 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

тому що тоді А.hpp не потрібно #include B.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можна оголосити за допомогою простого прямого оголошення B, тоді B.hвін складається двічі: при компілюванні B.cppта A.cpp(тому що Bвсе ще потрібен всередині Aметодів).

Але коли A.hвключає в себе B.h, він компілюється в чотири рази, при компіляції B.cpp, A.cpp, C.cppі , D.cppяк пізніше два нині побічно включає в себе B.hтакож.

Крім того, коли заголовок включений більш ніж один раз, препроцесор до сих пір читати його кожен раз. Вона пропустить обробку вмісту через захист#ifdef , але він все ще читає його і потребує пошуку кінця захисту, а значить, він повинен проаналізувати всі директиви препроцесора всередині.

(Як вже згадувалося в іншій відповіді, попередньо складені заголовки намагаються обійти це питання, але вони є власною башкою хробаків; в основному їх можна розумно використовувати для заголовків системи, і лише якщо ви не використовуєте занадто багато їх, але не для заголовки у вашому проекті)


+1, включення заголовка стає лише серйозною проблемою, коли ви маєте досить велику кількість класів, а не тоді, коли у вас є лише два класи A і B. Усі інші пости, здається, не вистачають цієї центральної точки.
Док Браун

2

Передача декларації набагато швидше розбирається, ніж цілий заголовок, який сам може містити ще більше файлів заголовків.

Крім того, якщо ви щось зміните у файлі заголовка для класу B, все, що включає цей заголовок, доведеться перекомпілювати. З попередньою декларацією це може бути лише вихідний файл, де розміщена реалізація A. Але якщо заголовок A насправді містив заголовок a.hppB.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.