2009-11-09 10 views
5

C++ nagłówkówC++ Nagłówki - Najlepsza praktyka przy tym

Jeśli mam A.cpp i Ah, jak BH, CH, dh

powinienem zrobić:

w Ah:

#include "b.h" 
#include "c.h" 
#include "d.h" 

w A.cpp:

#include "A.h" 

lub

w A.cpp:

#include "A.h" 
#include "b.h" 
#include "c.h" 
#include "d.h" 

Czy istnieją problemy z wydajnością? Oczywiste korzyści? Czy coś w tym złego?

Odpowiedz

0

Wolę nie licząc nagłówków w innych nagłówków - spowalnia kompilację i LEAS do okrągłej odniesienia

+0

@mgd - Jeśli mam nagłówek, który ma 10 obejmuje, a źródło ma 10 różnych obejmuje, powinienem po prostu przenieść obejmuje z nagłówka do źródła, więc źródło ma 20 obejmuje? –

+0

Zobacz bardziej kompletną odpowiedź alex. Jednym z wyjątków jest to, że jeśli masz prekompilowane nagłówki, to w tym przypadku wszystkie nagłówki platformy/frameworka/OS, które nigdy nie zmienią się w jeden plik i zawierają go wszędzie. –

0

nie byłoby problemów z wydajnością, to wszystko zrobić w czasie kompilacji, a nagłówki powinny być ustawione tak, że można” t być zawarte więcej niż jeden raz.

Nie wiem, czy istnieje standardowy sposób, aby to zrobić, ale wolę dołączyć wszystkie nagłówki, których potrzebuję w plikach źródłowych, i umieścić je w nagłówkach, jeśli coś w nagłówku tego wymaga (na przykład typedef z innego nagłówka)

+0

@Jeff - ach, w porządku, elementy w nagłówku mogą go potrzebować. ale czy nie otrzymaliby go, gdyby wszystkie nagłówki znajdowały się w pliku źródłowym? –

20

Powinieneś zawrzeć tylko to, co jest niezbędne do kompilacji; dodanie zbędnych elementów naruszy czas kompilacji, szczególnie w dużych projektach.

Każdy plik nagłówkowy powinien mieć możliwość samodzielnego kompilowania - to znaczy, jeśli masz plik źródłowy zawierający tylko ten nagłówek, powinien on skompilować się bez błędów. Plik nagłówkowy powinien zawierać nie więcej, niż jest to konieczne.

Staraj się używać zgłoszeń terminowych tak bardzo, jak to możliwe. Jeśli używasz klasę, ale plik nagłówka dotyczy jedynie wskaźniki/odniesień do obiektów tej klasy, to nie ma potrzeby obejmować definicję klasy - wystarczy użyć do przodu deklarację:

class SomeClass; 
// Can now use pointers/references to SomeClass 
// without needing the full definition 
+0

@Adam - czy masz przykład deklaracji przekazania i kiedy należy go użyć, a kiedy nie? –

+1

Świetna rada. Polecam również, aby plik .c skojarzony z .h zawierał .h jako pierwszy dołącz, zawsze. Ma to na celu upewnienie się, że wszystkie pliki .h mogą być zawsze kompilowane na własną rękę (zamiast polegać na kolejności włączania w pliku .c). –

+0

+1 dla ostatniej części, wcześniej zawsze zawierałem plik nagłówkowy –

3

To, co powiedział ci Adam Rosenfield, jest na miejscu. Przykładem kiedy można korzystać z przodu deklaracji jest:

#ifndef A_H_ 
#define A_H_ 

    #include "D.h" 
    class B; //forward declaration 
    class C; //forward declaration 

    class A 
    { 
    B *m_pb; //you can forward declare this one bacause it's a pointer and the compilier doesn't need to know the size of object B at this point in the code. include B.h in the cpp file. 
    C &m_rc; //you can also forware declare this one for the same reason, except it's a reference. 

    D m_d; //you cannot forward declare this one because the complier need to calc the size of object D. 

    }; 

#endif 
+0

(Mam nadzieję, że buduje, nie przetestowałem tego lol. Daj mi znać, jeśli masz z tym problem). – cchampion

6

Kluczem tutaj jest praktyka mając wokół każdego Foo.h złożyć straży takich jak:

#ifndef _FOO_H 
#define _FOO_H 

...rest of the .h file... 
#endif 

Zapobiega wielokrotnego inkluzje, z pętle i wszystkie takie towarzyszące okropności. Po upewnieniu się, że każdy plik włączający jest w ten sposób chroniony, szczegóły są mniej ważne.

Podoba mi się jedna zasada przewodnia Adam wyraża: upewnij się, że jeśli plik źródłowy zawiera tylko a.h, nie będzie nieuchronnie uzyskać błędy z powodu a.h zakładając, że inne pliki zostały dołączone przed nim - np. jeśli a.h wymaga wcześniejszego dodania, może i powinno po prostu dołączyć sam b.h (strażnicy wykonają to polecenie, jeśli b.h był już zawarte wcześniej)

Vice versa, plik źródłowy powinien zawierać nagłówki z którego jest wymagające coś (makra, deklaracje itp), nie założyć, że inne nagłówki po prostu przyjść w magiczny sposób, ponieważ ma włączone niektóre.

Jeśli korzystasz z klas według wartości, niestety, potrzebujesz wszystkich szczegółów dotyczących tej klasy w niektórych załącznikach .h. Ale w przypadku niektórych zastosowań za pomocą odnośników lub wskaźników wystarczą tylko puste class sic;. Np. Wszystkie inne rzeczy są sobie równe, jeśli klasa a może uciec wskaźnikowi do instancji klasy b (tj. Członek class b *my_bp; zamiast członka class b *my_b;) połączenie między plikami włączającymi może zostać osłabione (zmniejszając wiele ponownych kompilacji) - np b.h mógłby mieć trochę więcej niż class b; podczas gdy wszystkie krwawe szczegóły są w b_impl.h który znajduje się tylko nagłówki że naprawdę potrzebują ...

+0

Biorąc pod uwagę, że foo.h będzie nadal czytany za każdym razem, gdy jest włączony, nie jest to tak naprawdę "no-op". Byłoby to "no-op", gdybyś: #ifndef _FOO_H #include "foo.h" #endif – Bill

+0

Tak, ale to by zanieczyściło każde włączenie do nieczytelności. Do wszystkich celów i celów wielokrotne powtórzenie tego fragmentu znajdzie się w pamięci podręcznej systemu plików, więc nie ma żadnego uderzenia wydajności w "włączaniu" go ponownie - i zdecydowanie jest to no-op z punktu widzenia semantyki, co jest tak naprawdę sprawy. –

3

Odpowiedź: Niech A.h obejmują b.h, c.h i d.h tylko wtedy, gdy jest to potrzebne do spraw, aby kompilacja się udała. Zasada jest taka: w razie potrzeby uwzględnij tylko tyle kodu w pliku nagłówkowym. Wszystko, co nie jest natychmiast potrzebne w A.h, powinno być dołączone przez A.cpp.

Uzasadnienie: Im mniej kodu jest zawarty w plikach nagłówkowych, tym mniej prawdopodobne jest, że będziesz musiał przekompilować kod, który używa pliku nagłówka po przeprowadzeniu jakiejś zmiany. Jeśli istnieje wiele odniesień #include między różnymi plikami nagłówkowymi, zmiana któregokolwiek z nich będzie wymagać przebudowy wszystkich innych innych plików zawierających zmieniony plik nagłówkowy - rekursywny. Więc jeśli zdecydujesz się dotknąć jakiegoś pliku nagłówkowego, może to odbudować ogromne części twojego kodu.

Używając w swoich plikach nagłówkowych forward declarations, redukujesz sprzężenie plików źródłowych, dzięki czemu kompozycja staje się szybsza. Takie deklaracje forward mogą być używane w wielu sytuacjach więcej niż mogłoby się wydawać. Jako zasada, trzeba plik nagłówka t.h (która definiuje typ T) jeśli

  1. Zadeklaruj zmienną składową typu T (uwaga, to nie ma zawierać deklarowania pointer-to-tylko T).
  2. Napisz niektóre funkcje wbudowane, które mają dostęp do elementów obiektu typu T.

Robisz nie potrzebę włączenia deklarację T jeśli plik header tylko

  1. Deklaruje konstruktorzy/funkcje, które biorą odniesienia lub odsyłacze do T obiektu. Deklaracja funkcji zwracających obiekt T za pomocą wskaźnika, odwołania lub wartości.
  2. Deklaracja zmiennych składowych, które są odniesieniami lub wskaźnikami do obiektu T.

Rozważ tę deklarację klasową; które pliki należą do A, B i C czy naprawdę trzeba dołączyć ?:

class MyClass 
{ 
public: 
    MyClass(const A &a); 

    void set(const B &b); 
    void set(const B *b); 
    B getB(); 
    C getC(); 

private: 
    B *m_b; 
    C m_c; 
}; 

Wystarczy include pliku dla danego typu C, z powodu zmiennej składowej m_c. Często można również usunąć ten wymóg, nie deklarując bezpośrednio zmiennych członkowskich, ale za pomocą opaque pointer, aby ukryć wszystkie zmienne składowe w strukturze prywatnej, tak aby nie pojawiały się już w pliku nagłówkowym.

+0

W swoim przykładzie mieszacie A, B i C z X, Y i Z. – Bill

+0

@Bill: Masz rację, poprawiłem przykład, aby powiedzieć teraz A, B, C. –

+0

@FrerichRaabe idealna odpowiedź. +1 dla części "Reasoning:". – ParokshaX

Powiązane problemy