Garantire che le intestazioni siano esplicitamente incluse nel file CPP

8

Penso che in generale sia buona norma a #include l'intestazione per qualsiasi tipo utilizzato in un file CPP, indipendentemente da ciò che è già incluso nel file HPP. Quindi potrei #include <string> in entrambi i miei HPP e CPP, ad esempio, anche se potrei ancora compilare se l'ho saltato nel CPP. In questo modo non devo preoccuparmi se il mio HPP ha usato una dichiarazione in avanti o meno.

Esistono strumenti che possono imporre questo stile di codifica #include ? Dovrebbe applicare questo stile di codifica?

Poiché al preprocessore / compilatore non interessa se #include proviene da HPP o CPP, non ricevo feedback se dimentico di seguire questo stile.

    
posta M. Dudley 17.05.2013 - 19:34
fonte

7 risposte

7

Questo è uno di quei "dovrebbe" piuttosto che "deve" tipi di standard di codifica. Il motivo è che dovresti scrivere un parser C ++ per forzarlo.

Una regola molto comune per i file di intestazione è che devono stare da soli. Un file di intestazione non deve richiedere che alcuni altri file di intestazione siano #inclusi prima di includere l'intestazione in questione. Questo è un requisito verificabile. Dato un po 'di intestazione casuale foo.hh , il seguente dovrebbe compilare ed eseguire:

#include "foo.hh"
int main () {
   return 0;
}

Questa regola ha conseguenze sull'uso di altre classi in alcune intestazioni. A volte queste conseguenze possono essere evitate dichiarando in avanti quelle altre classi. Questo non è possibile con molte classi di librerie standard. Non c'è modo di inoltrare una istanza di template come std::string o std::vector<SomeType> . Devi #include di quelle intestazioni STL nell'intestazione anche se l'unico utilizzo del tipo è come argomento di una funzione.

Un altro problema riguarda cose che trascini involontariamente. Esempio: considera quanto segue:

file foo.cc:

#include "foo.hh"
#include "bar.hh"

void Foo::Foo () : bar() { /* body elided */ }

void Foo::do_something (int item) {
   ...
   bar.add_item (item);
   ...
}

Qui bar è un membro di dati di classe Foo che è di tipo Bar . Hai fatto la cosa giusta qui e hai #included bar.hh anche se sarebbe stato incluso nell'intestazione che definisce la classe Foo . Tuttavia, non hai incluso le cose usate da Bar::Bar() e Bar::add_item(int) . Ci sono molti casi in cui queste chiamate possono portare a ulteriori riferimenti esterni.

Se analizzi foo.o con uno strumento come nm , sembrerà che le funzioni in foo.cc chiamino tutti i tipi di materiale per i quali non hai fatto il #include appropriato. Quindi dovresti aggiungere #include direttive per quei riferimenti esterni accidentali foo.cc ? La risposta è assolutamente no. Il problema è che è molto difficile distinguere quelle funzioni chiamate incidentalmente da quelle che vengono chiamate direttamente.

    
risposta data 17.05.2013 - 22:38
fonte
2

Should I enforce this coding style?

Probabilmente no. La mia regola è questa: l'inclusione del file di intestazione non può essere dipendente dall'ordine.

Puoi verificarlo abbastanza facilmente con la semplice regola che il primo file incluso da x.c è x.h.

    
risposta data 17.05.2013 - 22:09
fonte
2

Se è necessario applicare una regola per cui determinati file di intestazione devono essere indipendenti, è possibile utilizzare gli strumenti già disponibili. Crea un makefile di base che compila singolarmente ciascun file di intestazione ma non genera un file oggetto. Sarai in grado di specificare quale modalità compilare il file di intestazione in (modalità C o C ++) e verificare che possa stare in piedi da sola. Puoi fare il ragionevole presupposto che l'output non contenga falsi positivi, tutte le dipendenze richieste siano dichiarate e l'output sia accurato.

Se stai utilizzando un IDE potresti ancora riuscire a farlo senza un makefile (a seconda dell'IDE). È sufficiente creare un progetto aggiuntivo, aggiungere i file di intestazione che si desidera verificare e modificare le impostazioni per compilarlo come file C o C ++. Ad esempio, in MSVC cambierebbe l'impostazione "Tipo oggetto" in "Proprietà di configurazione - > Generale".

    
risposta data 21.05.2013 - 05:58
fonte
1

Non penso che un tale strumento esista, ma sarei felice se qualche altra risposta mi confutasse.

Il problema con la scrittura di uno strumento di questo tipo è che segnala molto facilmente un risultato falso, quindi valuto il vantaggio netto di un tale strumento di essere vicino allo zero.

L'unico modo in cui un tale strumento potrebbe funzionare è se potesse reimpostare la sua tabella dei simboli solo al contenuto del file di intestazione elaborato, ma poi ti imbatti nel problema che le intestazioni che formano l'API esterna di una libreria delegano il dichiarazioni reali alle intestazioni interne.
Ad esempio, <string> nell'implementazione di libc ++ di GCC non dichiara nulla, ma include solo un gruppo di intestazioni interne che contengono le dichiarazioni effettive. Se lo strumento reimposta la sua tabella dei simboli solo a ciò che è stato dichiarato da <string> stesso, non sarebbe niente.
Potresti avere lo strumento di differenziare tra #include "" e #include <> , ma questo non ti aiuta se una libreria esterna utilizza #include "" per includere le intestazioni interne nell'API.

    
risposta data 17.05.2013 - 20:09
fonte
1

non raggiunge #Pragma once ? Puoi includere qualcosa tutte le volte che vuoi, direttamente o tramite gli accessi concatenati, e finché c'è una #Pragma once accanto a ciascuna di esse, l'intestazione viene inclusa solo una volta.

Per imporlo, forse potresti creare un sistema di build che include solo ogni intestazione da solo con alcune funzioni principali fittizie, solo per assicurarti che compili. #ifdef chain include per i migliori risultati con quel metodo di test.

    
risposta data 17.05.2013 - 20:30
fonte
1

Includere sempre i file di intestazione nel file CPP. Questo non solo riduce significativamente il tempo di compilazione, ma ti risparmia anche un sacco di problemi se decidi di andare con intestazioni precompilate. Nella mia esperienza, vale la pena fare anche la fatica delle dichiarazioni avanzate. Rompi la regola solo quando necessario.

    
risposta data 17.05.2013 - 21:13
fonte
0

Direi che ci sono sia vantaggi che svantaggi in questa convenzione. Da un lato è bello sapere esattamente cosa include il tuo file .cpp. D'altra parte, l'elenco degli include può facilmente crescere fino a ridurne le dimensioni.

Un modo per incoraggiare questa convenzione non è quello di includere qualcosa nelle intestazioni personali, ma solo nei file .cpp. Quindi qualsiasi file .cpp che utilizza l'intestazione non verrà compilato a meno che non includi esplicitamente tutte le altre intestazioni da cui dipende.

Probabilmente qui c'è un compromesso ragionevole. Ad esempio, potresti decidere di includere le intestazioni di libreria standard all'interno delle intestazioni personali, ma non di più.

    
risposta data 17.05.2013 - 19:42
fonte

Leggi altre domande sui tag