Przekaż deklarację vs dołącz

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.

Przeczytałem to tutaj. http://www.yolinux.com/TUTORIALS/LinuxTutorialC++CodingStyle.html .

Oznacza to, że jeśli klasa (klasa A) w pliku nagłówkowym nie musi używać rzeczywistej definicji niektórych klas (klasa B). W tym momencie możemy użyć deklaracji forward zamiast dołączania konkretnego pliku nagłówkowego (klasy B).

Pytanie: Jeśli klasa (klasa A) w nagłówku nie używa rzeczywistej definicji konkretnej klasy (klasa B), to w jaki sposób deklaracja przesyłania dalej pomaga skrócić czas kompilacji?

Nayana Adassuriya
źródło

Odpowiedzi:

12

Kompilator nie dba o to, czy klasa A korzysta z klasy B. Wie tylko, że gdy kompiluje się klasę A i nie ma wcześniejszej deklaracji klasy B (deklaracja przekazywana dalej lub w inny sposób), panikuje i oznacza ją jako błąd.

Ważne jest tutaj to, że kompilator wie, że nie próbowałeś skompilować programu po tym, jak Twój kot wszedł na klawiaturę i utworzył losowe litery, które mogą, ale nie muszą być interpretowane jako klasa.

Gdy zobaczy dołączenie, aby móc korzystać z zawartych w nim informacji, musi otworzyć plik i przeanalizować go (niezależnie od tego, czy rzeczywiście musi to zrobić). Jeśli plik ten zawiera następnie inne pliki, one również muszą zostać otwarte i przeanalizowane itp. Jeśli można tego uniknąć, ogólnie dobrym pomysłem jest użycie zamiast tego deklaracji przesyłania dalej.

Edycja : Wyjątkiem od tej reguły są prekompilowane nagłówki. W takim przypadku wszystkie nagłówki są kompilowane i zapisywane do przyszłych kompilacji. Jeśli nagłówki się nie zmieniają, kompilator może inteligentnie użyć wstępnie skompilowanych nagłówków z poprzednich kompilacji, a tym samym skrócić czas kompilacji, ale działa dobrze tylko wtedy, gdy często nie trzeba zmieniać nagłówków.

Neil
źródło
Dziękuję za wyjaśnienie. Następnie ok jak np myślisz istnieją trzy pliki nagłówkowe vehicle.h, bus.h, toybus.h. vehicle.hdołącz przez bus.hi bus.hdołącz przez toybus.h. więc jeśli coś zmienię bus.h. czy kompilator otwiera się i parsuje vehicle.hponownie? czy to kompiluje to ponownie?
Nayana Adassuriya
1
@NayanaAdassuriya Tak, jest dołączany i analizowany za każdym razem, dlatego też widzisz #pragma oncelub #ifndef __VEHICLE_H_wpisujesz deklaracje w plikach nagłówkowych, aby zapobiec dołączaniu takich plików wiele razy (lub wielokrotnemu użyciu, przynajmniej w przypadku ifndef).
Neil,
4

ponieważ wtedy A.hpp nie musi #include B.hpp

tak staje się A.hpp

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

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

więc kiedy A.hpp jest dołączony, B.hpp nie jest domyślnie dołączony, a wszystkie pliki, które zależą tylko od A.hpp, nie muszą być rekompilowane za każdym razem, gdy b.hpp zmienia się

maniak zapadkowy
źródło
ale w pliku źródłowym (A.cpp). należy dołączyć rzeczywisty plik nagłówka (Bh). Więc za każdym razem musi się kompilować. Wreszcie w obie strony Bh musi się ponownie skompilować ze zmianami. Coś innego?
Nayana Adassuriya
@NayanaAdassuriya nie, ponieważ A używa tylko wskaźnika do B, a zmiany w B nie będą miały wpływu na A.hpp (lub pliki, które go zawierają)
maniak
@NayanaAdassuriya: Tak, A.cpp będzie musiał ponownie skompilować (jeśli użyje definicji B w ciałach metod A, ale zwykle tak jest), ale C.cpp, który używa A, ale nie B, nie zrobi tego.
Jan Hudec
3

Pamiętaj, że preprocesor C / C ++ to osobny, czysto tekstowy etap przetwarzania. Do #includedyrektywa ściąga treści zawarte nagłówku i kompilator musi ją przeanalizować. Co więcej, kompilacja każdego z nich .cppjest całkowicie osobna, więc fakt, że kompilator właśnie przeanalizował B.hpodczas kompilacji B.cpp, nie pomaga, gdy jest potrzebny ponownie podczas kompilacji A.cpp. I znowu podczas kompilacji C.cpp. I D.cpp… I tak dalej. I każdy z tych plików musi zostać ponownie skompilowany, jeśli jakikolwiek plik w nim się zmienił.

Powiedzmy, że klasa Aużywa klasy Bi klas Ci Dużywa klasy A, ale nie trzeba manipulować B. Jeśli klasa Amoże być zadeklarowana za pomocą tylko deklaracji forward B, niż B.hjest kompilowana dwukrotnie: podczas kompilacji B.cppi A.cpp(ponieważ Bnadal jest potrzebna w ramach Ametod).

Ale kiedy A.hobejmuje B.h, to jest kompilowany cztery razy, podczas kompilacji B.cpp, A.cpp, C.cppi D.cppjak później dwa teraz pośrednio obejmuje B.hteż.

Również, gdy nagłówek jest dołączany więcej niż jeden raz, preprocesor nadal musi go czytać za każdym razem. Pominie przetwarzanie zawartości ze względu na ochronę#ifdef , ale nadal ją odczytuje i musi szukać końca osłony, co oznacza, że ​​musi przeanalizować wszystkie zawarte w niej dyrektywy preprocesora.

(Jak wspomniano w drugiej odpowiedzi, prekompilowane nagłówki próbują obejść ten problem, ale są one własną puszką robaków; w zasadzie można je rozsądnie wykorzystać do nagłówków systemowych i tylko wtedy, gdy nie używa się ich zbyt wielu, ale nie do nagłówki w twoim projekcie)

Jan Hudec
źródło
+1, nagłówki-włączone stają się poważnym problemem, gdy masz dość ogromną liczbę klas, a nie tylko mając dwie klasy A i B. Wszystkie inne posty wydają się omijać ten centralny punkt.
Doc Brown
2

Deklaracja przesyłania dalej jest znacznie szybciej analizowana niż cały plik nagłówka, który sam może zawierać jeszcze więcej plików nagłówka.

Ponadto, jeśli zmienisz coś w pliku nagłówkowym dla klasy B, wszystko łącznie z tym nagłówkiem będzie musiało zostać ponownie skompilowane. W przypadku deklaracji przesyłania może to być tylko plik źródłowy, w którym rezyduje implementacja A. Ale jeśli nagłówek A faktycznie zawiera nagłówek B, wszystko łącznie z tym a.hppzostanie ponownie skompilowane, nawet jeśli nie używa niczego z B.

Benjamin Kloster
źródło