Garantire che le intestazioni siano esplicitamente incluse nel file CPP


9

Penso che sia generalmente una buona pratica per #includel'intestazione per tutti i tipi utilizzati in un file CPP, indipendentemente da ciò che è già incluso tramite il file HPP. Quindi, #include <string>sia nel mio HPP che nel mio CPP, ad esempio, potrei comunque compilare se lo ignorassi nel CPP. In questo modo non devo preoccuparmi se il mio HPP abbia usato o meno una dichiarazione anticipata.

Esistono strumenti in grado di applicare questo #includestile di codifica? Devo applicare questo stile di codifica?

Dato che al preprocessore / compilatore non importa se #includeproviene dall'HPP o dal CPP, non ricevo feedback se dimentico di seguire questo stile.

Risposte:


7

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

Una regola molto comune per i file di intestazione è che devono essere autonomi. 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 header 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 tali conseguenze possono essere evitate dichiarando in avanti quelle altre classi. Questo non è possibile con molte classi di libreria standard. Non è possibile inoltrare un'istanza del modello come std::stringo std::vector<SomeType>. Devi #includeusare le intestazioni STL nell'intestazione anche se l'unico uso del tipo è come argomento di una funzione.

Un altro problema riguarda le cose che si trascinano per errore. Esempio: considerare 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);
   ...
}

Ecco barun Foomembro di dati di classe che è di tipo Bar. Hai fatto la cosa giusta qui e hai #included bar.hh anche se avrebbe dovuto essere incluso nell'intestazione che definisce la classe Foo. Tuttavia, non hai incluso le cose utilizzate da Bar::Bar()e Bar::add_item(int). Esistono molti casi in cui queste chiamate possono comportare riferimenti esterni aggiuntivi.

Se analizzi foo.ocon uno strumento come nm, sembrerà che le funzioni in foo.ccchiamino tutti i tipi di cose per le quali non hai fatto le cose appropriate #include. Quindi dovresti aggiungere #includedirettive per quei riferimenti esterni accidentali foo.cc? La risposta è assolutamente no. Il problema è che è molto difficile distinguere quelle funzioni chiamate per inciso da quelle chiamate direttamente.


2

Devo applicare questo stile di codifica?

Probabilmente no. La mia regola è questa: l'inclusione del file header non può dipendere dall'ordine.

Puoi verificarlo abbastanza facilmente con la semplice regola che il primo file incluso da xc è xh


1
Puoi approfondire questo? Non riesco a vedere come ciò verificherebbe davvero l'indipendenza dell'ordine.
detly

1
non garantisce in realtà l'indipendenza dell'ordine, ma garantisce che #include "x.h"funzionerà senza richiedere alcun precedente #include. Va bene se non abusi #define.
Kevin Cline,

1
Oh, capisco. Comunque una buona idea allora.
detly

2

Se devi imporre una regola che determinati file di intestazione devono essere autonomi, puoi utilizzare gli strumenti che hai già. Crea un makefile di base che compili ogni file di intestazione singolarmente ma non generi un file oggetto. Sarai in grado di specificare in quale modalità compilare il file di intestazione (modalità C o C ++) e verificare che possa essere autonomo. È possibile supporre che l'output non contenga falsi positivi, che tutte le dipendenze richieste siano dichiarate e che l'output sia accurato.

Se stai usando un IDE potresti essere ancora in grado di farlo senza un makefile (a seconda del tuo IDE). Basta 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 è necessario modificare l'impostazione "Tipo di elemento" in "Proprietà di configurazione-> Generale".


1

Non credo che esista un simile strumento, ma sarei felice se qualche altra risposta mi smentisse.

Il problema con la scrittura di un tale strumento è che riporta molto facilmente un risultato falso, quindi stima che il vantaggio netto di tale strumento sia vicino allo zero.

L'unico modo in cui uno strumento del genere potrebbe funzionare è se fosse in grado di ripristinare la tabella dei simboli solo al contenuto del file di intestazione che ha elaborato, ma poi si verifica il problema che le intestazioni che formano l'API esterna di una libreria delegano le dichiarazioni effettive a intestazioni interne.
Ad esempio, <string>nell'implementazione di GCC libc ++ non si dichiara nulla, ma include solo un gruppo di intestazioni interne che contengono le dichiarazioni effettive. Se lo strumento ripristina la sua tabella dei simboli solo su ciò che è stato dichiarato da <string>solo, non sarebbe nulla.
Potresti avere lo strumento per distinguere tra #include ""e #include <>, ma ciò non ti aiuta se una libreria esterna utilizza #include ""per includere le sue intestazioni interne nell'API.


1

non ci riesci #Pragma once? Puoi includere qualcosa tutte le volte che vuoi, direttamente o tramite inclusioni concatenate, e fintanto che c'è una #Pragma onceaccanto a ciascuna di esse, l'intestazione è inclusa solo una volta.

Per quanto riguarda la sua applicazione, forse potresti creare un sistema di compilazione che include solo ogni intestazione da solo con qualche funzione principale fittizia, solo per assicurarti che venga compilata. #ifdefla catena include i migliori risultati con quel metodo di prova.


1

Includi sempre i file di intestazione nel file CPP. Questo non solo riduce significativamente i tempi di compilazione, ma ti fa anche risparmiare un sacco di problemi se decidi di utilizzare intestazioni precompilate. Nella mia esperienza, vale la pena anche fare il disturbo delle dichiarazioni anticipate. Rompere la regola solo quando necessario.


Puoi spiegare come questo "ridurrà significativamente i tempi di compilazione"?
Mawg dice di ripristinare Monica

1
Ciò garantisce che ogni unità di compilazione (file CPP) inserisca solo il minimo dei file include al momento della compilazione. Altrimenti, se inserisci inclusioni in file H, le catene di false dipendenze si formano rapidamente e finisci per estrarre tutte le inclusioni con ogni compilazione.
Gvozden,

Con include guards, potrei avere una domanda "significativa", ma suppongo che l'accesso al disco (in ordine per scoprire quelle imprecisioni sponde) sia "lento", quindi cadrà il punto. Grazie per il chiarimento
Mawg dice di reintegrare Monica

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 delle inclusioni può facilmente crescere fino a dimensioni ridicole.

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

Probabilmente qualche ragionevole compromesso è in ordine qui. Ad esempio, potresti decidere che è giusto includere intestazioni di libreria standard all'interno delle tue intestazioni, ma non di più.


3
Se lasci include le intestazioni, le inclusioni iniziano a importare ... e voglio assolutamente evitarlo.
M. Dudley,

1
-1: crea un incubo di dipendenza. Un potenziamento di xh potrebbe richiedere una modifica in OGNI file .cpp che include xh
kevin cline

1
@ M.Dudley, Come ho detto, ci sono vantaggi e svantaggi.
Dima,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.