Diverse ragioni
File di intestazione
Ogni singola unità di compilazione richiede che centinaia o persino migliaia di intestazioni siano caricate (1) e (2) compilate. Ognuno di essi deve in genere essere ricompilato per ogni unità di compilazione, poiché il preprocessore garantisce che il risultato della compilazione di un'intestazione possa variare tra ogni unità di compilazione. (Una macro può essere definita in un'unità di compilazione che modifica il contenuto dell'intestazione).
Questo è probabilmente il motivo principale, poiché richiede la compilazione di enormi quantità di codice per ogni unità di compilazione e, inoltre, ogni intestazione deve essere compilata più volte (una volta per ogni unità di compilazione che lo include).
Collegamento
Una volta compilati, tutti i file degli oggetti devono essere collegati insieme. Questo è fondamentalmente un processo monolitico che non può essere ben parallelizzato e deve elaborare l'intero progetto.
parsing
La sintassi è estremamente complicata da analizzare, dipende fortemente dal contesto ed è molto difficile da chiarire. Questo richiede molto tempo.
Modelli
In C #, List<T>
è l'unico tipo che viene compilato, indipendentemente da quante istanze di Elenco hai nel tuo programma. In C ++, vector<int>
è un tipo completamente separato da vector<float>
, e ognuno dovrà essere compilato separatamente.
Aggiungete a ciò che i template formano un "sotto-linguaggio" completo di Turing che il compilatore deve interpretare, e questo può diventare ridicolmente complicato. Anche il codice di metaprogrammazione dei modelli relativamente semplice può definire modelli ricorsivi che creano dozzine e dozzine di istanze di modelli. I modelli possono anche risultare in tipi estremamente complessi, con nomi ridicolmente lunghi, aggiungendo molto lavoro extra al linker. (Deve confrontare molti nomi di simboli, e se questi nomi possono crescere in molte migliaia di caratteri, ciò può diventare abbastanza costoso).
E, naturalmente, esacerbano i problemi con i file di intestazione, poiché i modelli devono generalmente essere definiti nelle intestazioni, il che significa che molto più codice deve essere analizzato e compilato per ogni unità di compilazione. Nel semplice codice C, un'intestazione in genere contiene solo dichiarazioni forward, ma pochissimo codice effettivo. In C ++, non è raro che quasi tutto il codice risieda nei file di intestazione.
Ottimizzazione
C ++ consente alcune ottimizzazioni molto drammatiche. C # o Java non consentono di eliminare completamente le classi (devono essere presenti a scopo di riflessione), ma anche un semplice metaprogramma di modello C ++ può facilmente generare dozzine o centinaia di classi, tutte incorporate ed eliminate di nuovo nell'ottimizzazione fase.
Inoltre, un programma C ++ deve essere completamente ottimizzato dal compilatore. Il programma AC # può fare affidamento sul compilatore JIT per eseguire ulteriori ottimizzazioni in fase di caricamento, C ++ non ottiene tali "seconde possibilità". Ciò che genera il compilatore è ottimizzato come otterrà.
Macchina
Il C ++ viene compilato in codice macchina che può essere leggermente più complicato dell'utilizzo del bytecode Java o .NET (specialmente nel caso di x86). (Questo è menzionato per completezza solo perché è stato menzionato nei commenti e simili. In pratica, è improbabile che questo passaggio richieda più di una piccola frazione del tempo totale di compilazione).
Conclusione
La maggior parte di questi fattori sono condivisi dal codice C, che in realtà viene compilato in modo abbastanza efficiente. Il passaggio di analisi è molto più complicato in C ++ e può richiedere molto più tempo, ma il principale offensore è probabilmente il template. Sono utili e rendono C ++ un linguaggio molto più potente, ma prendono anche il loro pedaggio in termini di velocità di compilazione.