Несколько причин
Заголовочные файлы
Каждый отдельный модуль компиляции требует, чтобы (1) загружались и (2) компилировались сотни или даже тысячи заголовков.
Каждый из них, как правило, должен быть перекомпилирован для каждого модуля компиляции,
потому что препроцессор гарантирует, что результат компиляции заголовка может варьироваться в зависимости от каждой единицы компиляции.
(Макрос может быть определен в одном модуле компиляции, который изменяет содержимое заголовка).
Это, вероятно, основная причина , так как для каждой единицы компиляции требуется огромное количество кода,
и, кроме того, каждый заголовок должен быть скомпилирован несколько раз
(один раз за каждую включенную в него единицу компиляции).
Linking
После компиляции все объектные файлы должны быть связаны друг с другом.
По сути, это монолитный процесс, который нельзя распараллелить, и он должен обрабатывать весь ваш проект.
Синтаксический
Синтаксис чрезвычайно сложен для анализа, сильно зависит от контекста, и его очень сложно устранить.
Это занимает много времени.
Шаблоны
В C # List<T>
- это единственный тип, который компилируется, независимо от того, сколько экземпляров List у вас есть в вашей программе.
В C ++ vector<int>
- это совершенно отдельный тип от vector<float>
, и каждый из них должен быть скомпилирован отдельно.
Добавьте к этому, что шаблоны составляют полный «подъязык», полный по Тьюрингу, который должен интерпретировать компилятор,
и это может стать смехотворно сложным.
Даже относительно простой шаблон метапрограммирования шаблонов может определять рекурсивные шаблоны, которые создают десятки и десятки экземпляров шаблонов.
Шаблоны также могут приводить к чрезвычайно сложным типам с нелепо длинными именами, добавляя много дополнительной работы компоновщику.
(Он должен сравнивать множество имен символов, и если эти имена могут вырасти во многие тысячи символов, это может стать довольно дорогим).
И, конечно, они усугубляют проблемы с заголовочными файлами, потому что шаблоны обычно должны быть определены в заголовках,
это означает, что для каждой единицы компиляции нужно анализировать и компилировать гораздо больше кода.
В простом C-коде заголовок обычно содержит только прямые объявления, но очень мало реального кода.
В C ++ практически весь код находится в заголовочных файлах.
Оптимизация
C ++ допускает некоторые весьма существенные оптимизации.
C # или Java не позволяют полностью исключать классы (они должны быть там для целей отражения),
но даже простая метапрограмма шаблона C ++ может легко генерировать десятки или сотни классов,
все они встраиваются и снова исключаются на этапе оптимизации.
Более того, программа на C ++ должна быть полностью оптимизирована компилятором.
Программа на C # может полагаться на JIT-компилятор для выполнения дополнительных оптимизаций во время загрузки,
C ++ не имеет таких «вторых шансов». То, что генерирует компилятор, так же оптимизировано, как и собирается.
машина
C ++ компилируется в машинный код, который может быть несколько сложнее, чем использование байт-кода Java или .NET (особенно в случае x86).
(Это упомянуто из полноты только потому, что это было упомянуто в комментариях и тому подобное.
На практике этот шаг вряд ли займет более малую долю от общего времени компиляции).
Заключение
Большинство из этих факторов являются общими для кода C, который на самом деле компилируется довольно эффективно.
Этап разбора намного сложнее в C ++ и может занимать значительно больше времени, но, вероятно, основным нарушителем являются шаблоны.
Они полезны и делают C ++ гораздо более мощным языком, но они также берут свое с точки зрения скорости компиляции.