Реализация в хедер .h файле или разделённая на .h и .cpp?
Подскажите пожалуйста, какая разница между тем, что я напишу реализацию какого то условного класса или функции в одном хедер файле или разделю их на реализацию в .cpp и прототип в .h ?
Правильно я понимаю, что если мне нет необходимости потом компилировать код в библиотеку, то разницы между этими методами абсолютно нет ?
Ответы (2 шт):
Различия можно раскидать условно на две стопки: различия технологические и человеческий фактор.
Главный технический фактор: это можно сделать не всегда. Отделение объявлений от реализации - это важная часть того, как работает компиляция на C++. Если у вас есть циклическая зависимость в объявлениях, вы будете вынуждены иногда использовать forward-declaration, а иногда - полное описание класса. Вот как раз вариант с отдельными файлами для объявления и реализации дает решение этого вопроса - в файле .h вы работаете с forward-declaration, в файле .cpp - с полным объявлением.
(Да, есть альтернативный вариант (предложен
@user7860670) при помощи ювелирной расстановки #include, но человеческий фактор мешает лично мне рассматривать его всерьез, так это требует от разработчика следования строгим правилам, иначе - матомный взрыв из нетривиальных ошибок в исполнении компилятора).
Также утверждается, что это катастрофически сокращает время сборки и размер выходного файла, что выглядит перспективно, но достигается ценой головной боли разработчиков.
Лично на мой взгляд, размер выходного файла и время сборки - это минорные факторы, а вот тот факт, что разработчик занят выверкой имен и разгребанием ошибок, вместо решения прикладной задачи - это главный шоустоппер такого решения. Ферма из тредрипперов под сборку обойдется дешевле времени разработчиков и реабилитации их после нудной работы по приведению хидеров в порядок.
Далее уже всякая лирика:
Технически, тела всего что засунуто в объявление класса, будут расцениваться компилятором как inline, следовательно он будет пытаться, по возможности, затолкать эти тела прямо на место, откуда они вызываются - в зависимости от настроек оптимизации, разумеется. Это может повлиять на скорость работы программы и на ее размер, причем в любую сторону.
Тела отдельно стоящих функций же, вы будете вынуждены пометить как inline, иначе компилятор работать откажется.
Теперь кратко про человеческий фактор - это уже вопрос привычки и вкуса.
Реализация всего в заголовочных файлах известна как концепция SCU. В качестве преимуществ (для крупных проектов) вы получаете
- Радикальное сокращение времени сборки. Это основная причина для перехода на SCU. Когда у вас есть проект с
nединицами трансляции, время сборки будет расти ~линейно при добавлении каждой новой единицы трансляции. В то же время при использовании SCU время сборки будет расти только ~логарифмически и добавление новых классов в проект практически никак не будет сказываться на времени сборки. - Сокращение памяти, требуемой для сборки. Дисковой и оперативной. Хотя одна "большая" единица трансляции будет очевидно занимать куда больше места, чем любая из "маленьких", общий размер маленьких единиц трансляции будет куда больше.
- Выигрыш по оптимизации. Очевидно, что все автоматически становится inline.
- Пропадает страх случайно запустить пересборку всего проекта с 0. Это особенно важно в контексте Непрерывной Интеграции, так как это то, чем постоянно занимается CI сервер.
В качестве сложностей:
- Разработчики должны четко соблюдать дисциплину организации заголовочных файлов. Header guards, единообразный порядок директив
#include, обязательное обеспечение самодостаточности каждого заголовочного файла, правильный форвардинг, единство соглашения об именованиях и т.п. Проблема в том, что инструментов для соблюдения этого всего нет, а отход от установленных правил приводит к сообщениям об ошибках, которые еще более мутные, чем обычно. - Потенциальное увеличение числа файлов в проекте. Смотрите этот ответ.
- Больше не получится сражаться на мечах, отмазываясь, что код компилируется.
некоторые числа:
- время полной сборки упало с ~14 минут до ~20 секунд (1 ядро), размер исполняемого файла сократился в 3 раза (результат преобразования существующего проекта в SCU по моему опыту)
- real-world use cases: CppCon 2014: Nicolas Fleury "C++ in Huge AAA Games", Chromium Jumbo / Unity builds ("is can save hours for a full build")