I file di intestazione sono effettivamente buoni? [chiuso]


20

Trovo che i file di intestazione siano utili durante la navigazione dei file sorgente C ++, perché forniscono un "sommario" di tutte le funzioni e i membri dei dati di una classe. Perché così tante altre lingue (come Ruby, Python, Java, ecc.) Non hanno una funzionalità come questa? È un'area in cui la verbosità del C ++ è utile?


Non ho familiarità con gli strumenti in C e C ++, ma la maggior parte degli IDE / editor di codice in cui ho lavorato hanno una vista "struttura" o "struttura".
Michael K,

1
I file di intestazione non sono buoni - sono necessari! - Vedi anche questa risposta su SO: stackoverflow.com/a/1319570/158483
Pete

Risposte:


31

Lo scopo originale dei file di intestazione era quello di consentire la compilazione a passaggio singolo e la modularità in C. Dichiarando i metodi prima che fossero utilizzati, consentiva solo il passaggio a compilazione singolo. Questa epoca è passata da molto tempo grazie ai nostri potenti computer che sono in grado di eseguire compilazioni multi-pass senza problemi e talvolta anche più velocemente dei compilatori C ++.

Il C ++ essendo retrocompatibile con C era necessario per mantenere i file di intestazione, ma ne aggiungeva molti altri, il che ha portato a una progettazione piuttosto problematica. Altro in FQA .

Per la modularità, i file di intestazione erano necessari come metadati sul codice nei moduli. Per esempio. quali metodi (e in classi C ++) sono disponibili in quale libreria. Era ovvio che lo sviluppatore lo scrivesse, perché il tempo di compilazione era costoso. Al giorno d'oggi, non è un problema che il compilatore generi questi metadati dal codice stesso. I linguaggi Java e .NET lo fanno normalmente.

Quindi no. I file di intestazione non sono buoni. Lo erano quando dovevamo ancora avere compilatore e linker su floppy separati e la compilazione impiegava 30 minuti. Al giorno d'oggi, si mettono solo in mezzo e sono segni di cattiva progettazione.


8
Sono d'accordo con la maggior parte di ciò che scrivi, ma l'ultimo paragrafo rende le cose un po 'semplici. I file di intestazione continuano a essere utili per la definizione di interfacce pubbliche.
Benjamin Bannier,

3
@honk È quello a cui stavo arrivando con la mia domanda. Ma penso che gli IDE moderni (come suggerito da ElYusubov) in qualche modo neghino questo.
Matt Fichman,

5
È possibile aggiungere che il comitato C ++ sta lavorando su moduli che renderebbero C e C ++ in grado di funzionare senza intestazioni O con intestazioni utilizzate in modo più efficiente. Ciò renderebbe questa risposta più giusta.
Klaim

2
@MattFichman: La generazione di alcuni file di intestazione è una cosa (che la maggior parte degli editor / IDE del programmatore può fare automaticamente in qualche modo), ma il punto su un'API pubblica è che deve effettivamente essere definita e progettata da un vero essere umano.
Benjamin Bannier,

2
@honk Sì. Ma non vi è alcun motivo per definirlo in file separati. Può essere nello stesso codice dell'implementazione. Proprio come C # lo sta facendo.
Euforico,

17

Sebbene possano essere utili come forma di documentazione, il sistema che circonda i file di intestazione è straordinariamente inefficiente.

C è stato progettato in modo tale che ogni passaggio di compilazione costruisca un singolo modulo; ogni file di origine viene compilato in un'esecuzione separata del compilatore. I file di intestazione, d'altra parte, vengono iniettati in quel passaggio di compilazione per ciascuno dei file di origine che li fanno riferimento.

Ciò significa che se il file di intestazione è incluso in 300 file di origine, viene analizzato e compilato più e più volte, 300 volte separate durante la creazione del programma. La stessa identica cosa con lo stesso identico risultato, ancora e ancora. Questo è un enorme spreco di tempo ed è uno dei motivi principali per cui i programmi C e C ++ impiegano così tanto tempo per essere costruiti.

Tutte le lingue moderne evitano intenzionalmente questa assurda piccola inefficienza. Invece, in genere nei linguaggi compilati i metadati necessari sono archiviati nell'output di compilazione, consentendo al file compilato di fungere da una sorta di riferimento di ricerca rapida che descrive il contenuto del file compilato. Tutti i vantaggi di un file di intestazione, creato automaticamente senza alcun lavoro aggiuntivo da parte tua.

Alternativamente nelle lingue interpretate, ogni modulo che viene caricato rimane in memoria. Fare riferimento o includere o richiedere una libreria leggerà e compilerà il codice sorgente associato, che rimane residente fino al termine del programma. Se lo richiedi anche altrove, nessun lavoro aggiuntivo in quanto è già stato caricato.

In entrambi i casi, è possibile "sfogliare" i dati creati da questo passaggio utilizzando gli strumenti della lingua. In genere l'IDE avrà un browser di classe di qualche tipo. E se la lingua ha un REPL, può anche essere usata spesso per generare un riepilogo della documentazione di tutti gli oggetti caricati.


5
non del tutto vero - la maggior parte, se non tutti, i compilatori pre-compilano le intestazioni come le vedono, quindi includerle nella successiva unità di compilazione non è peggio che trovare il blocco di codice pre-compilato. Non è diverso, diciamo, dal codice .NET che "costruisce" ripetutamente system.data.types per ogni file C # che compili. Il motivo per cui i programmi C / C ++ impiegano così tanto tempo è perché sono effettivamente compilati (e ottimizzati). Tutto ciò che serve a un programma .NET deve essere analizzato in IL, il runtime esegue la compilazione effettiva tramite JIT. Detto questo, anche la compilazione di 300 file sorgente in codice C # richiede del tempo.
gbjbaanb,

7

Non è chiaro cosa si intende sfogliando il file per funzioni e membri dei dati. Tuttavia, la maggior parte degli IDE fornisce strumenti per esplorare la classe e vedere i membri dei dati della classe.

Ad esempio: Visual Studio ha Class Viewe Object browserche fornisce perfettamente la funzionalità richiesta. Come nelle seguenti schermate.

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine


4

Un ulteriore svantaggio dei file di intestazione è che il programma dipende dall'ordine della loro inclusione e che il programmatore deve adottare ulteriori misure per garantire la correttezza.

Ciò, unito al sistema macro di C (ereditato da C ++), porta a molte insidie ​​e situazioni confuse.

Per illustrare, se un'intestazione definiva un simbolo che utilizzava macro per il suo utilizzo e un'altra intestazione utilizzava il simbolo in un altro modo come un nome per una funzione, l'ordine di inclusione avrebbe influenzato notevolmente il risultato finale. Ci sono molti esempi simili.


2

Mi sono sempre piaciuti i file di intestazione in quanto forniscono una forma di interfaccia per l'implementazione, insieme ad alcune informazioni extra come le variabili di classe, il tutto in un unico file di facile visualizzazione.

Vedo un sacco di codice C # (che non necessita di 2 file per classe) scritto usando 2 file per classe: uno l'implementazione della classe effettiva e un altro con un'interfaccia. Questo design è buono per deridere (essenziale in alcuni sistemi) e aiuta a definire la documentazione della classe senza dover usare l'IDE per visualizzare i metadati compilati. Andrei così lontano per dire la sua buona pratica.

Quindi C / C ++ che impone un equivalente (di sorta) di un'interfaccia nei file di intestazione è una buona cosa.

So che ci sono sostenitori di altri sistemi a cui non piacciono per ragioni tra cui "è più difficile semplicemente decifrare il codice se devi mettere le cose in 2 file", ma il mio atteggiamento è che hackerare il codice non è comunque una buona pratica e una volta che inizi a scrivere / progettare il codice con un po 'più di pensiero, allora definirai le intestazioni / le interfacce come ovvio.


Interfaccia e implementazione in diversi file? Se hai bisogno di questo in base alla progettazione, questo è ancora molto comune per definire l'interfaccia (o la classe astratta) in linguaggi come Java / C # in un file e nascondere le implementazioni in altri file. Ciò non richiede file di intestazione vecchi e difficili.
Petter Nordlander,

eh? non è quello che ho detto: tu definisci l'interfaccia e l'implementazione della classe in 2 file.
gbjbaanb,

2
bene, ciò non richiede file di intestazione e che non rende i file di intestazione migliori. Sì, sono necessari in linguaggi legacy, ma come concetto non sono necessari per dividere l'interfaccia / implementazione in file diversi (in realtà, peggiorano la situazione).
Petter Nordlander,

@Petter Penso che tu fraintenda, concettualmente non sono diversi - esiste un'intestazione per fornire un'interfaccia nel linguaggio C, e sebbene tu abbia ragione - puoi combinare il 2 (come molti fanno con il codice C solo dell'intestazione) non è un le migliori pratiche che ho visto sono seguite in molti luoghi. Tutti gli sviluppatori di C # che ho visto, ad esempio, hanno separato l'interfaccia dalla classe. È buona norma eseguire questa operazione poiché è possibile includere il file di interfaccia in più altri file senza inquinamento. Ora, se pensi che questa divisione sia una buona pratica (lo è) allora il modo per raggiungerla in C è usando i file header.
gbjbaanb,

Se le intestazioni fossero effettivamente utili per separare l'interfaccia e l'implementazione, cose come PImpl non sarebbero necessarie per nascondere i componenti privati ​​e disaccoppiare gli utenti finali dal dover ricompilare contro di loro. Invece, otteniamo il peggio di entrambi i mondi. Le intestazioni fingono una facciata di separazione rapidamente respinta, mentre aprono una miriade di insidie, che richiedono un codice di caldaia dettagliato se è necessario aggirarle.
underscore_d

1

In realtà direi che i file delle intestazioni non sono grandi perché confondono l'interfaccia e l'implementazione. L'intento con la programmazione in generale, e in particolare OOP, è avere un'interfaccia definita e nascondere i dettagli dell'implementazione, ma un file di intestazione C ++ mostra sia i metodi, l'ereditarietà e i membri pubblici (interfaccia) sia i metodi privati ​​e i membri privati (una parte dell'attuazione). Per non parlare del fatto che in alcuni casi si finisce per incorporare codice o costruttori nel file di intestazione e alcune librerie includono il codice modello nelle intestazioni, che mescola davvero l'implementazione con l'interfaccia.

L'intento originale, a mio avviso, era di consentire al codice di utilizzare altre librerie, oggetti, ecc. Senza dover importare l'intero contenuto dello script. Tutto ciò che serve è l'intestazione da compilare e collegare. Risparmia tempo e cicli in quel modo. In quel caso, è un'idea decente, ma è solo un modo per risolvere questi problemi.

Per quanto riguarda la navigazione nella struttura del programma, la maggior parte degli IDE offre tale capacità e ci sono molti strumenti che elimineranno le interfacce, eseguiranno l'analisi del codice, la decompilazione, ecc. In modo da poter vedere cosa sta succedendo sotto le copertine.

Perché perché altre lingue non implementano la stessa funzione? Bene, perché altre lingue provengono da altre persone e quei designer / creatori hanno una visione diversa di come dovrebbero funzionare le cose.

La risposta migliore è attenersi a ciò che fa il lavoro che devi fare e ti rende felice.


0

In molti linguaggi di programmazione, quando un programma è suddiviso in più unità di compilazione, il codice in una unità sarà in grado di usare le cose definite in un'altra solo se il compilatore ha qualche mezzo per sapere quali sono. Invece di richiedere che un compilatore che elabora un'unità di compilazione esamini l'intero testo di ogni unità di compilazione che definisca qualsiasi cosa utilizzata all'interno dell'unità presente, è meglio che il compilatore riceva, durante l'elaborazione di ciascuna unità, il testo completo dell'unità da compilare con poche informazioni da tutti gli altri.

In C, il programmatore è responsabile della creazione di due file per ogni unità: uno contenente informazioni che saranno necessarie solo durante la compilazione di quell'unità e uno contenente informazioni che saranno necessarie anche durante la compilazione di altre unità. Questo è un design piuttosto sgradevole, caotico, ma evita la necessità che uno standard di linguaggio specifichi qualcosa su come i compilatori dovrebbero generare ed elaborare qualsiasi file intermedio. Molte altre lingue usano un approccio diverso, in cui un compilatore genererà da ciascun file sorgente un file intermedio che descrive tutto ciò che si presume sia accessibile al codice esterno. Questo approccio evita la necessità di avere informazioni duplicate in due file di origine, ma richiede che una piattaforma di compilazione abbia definito la semantica dei file in un modo che non è richiesto per C.

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.