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