Как говорят, «или крест снимите, или трусы наденьте». И учите понятие «единица компиляции».
По какой схеме устроен ваш проект? «Одна единица компиляции» или «много единиц компиляции»?
Си недалеко ушёл от ассемблеров. А в ассемблерах программа компилировалась по частям и собиралась воедино линкером (компоновщиком, редактором связей) — в те времена кода было много, а данных мало. Многие из ошибок невозможно было определить, не запустив линкер. Си++ пользуется многими из архитектурных особенностей ассемблеров и Си — по крайней мере ни одно из модульных решений не стало рекомендацией (кроме костыля
extern template class
).
Но как говорить «переменная/функция есть, такого-то типа и в другой единице компиляции»? Для этого есть прототипы функций и extern-определения переменных. Их обычно вносят в заголовочные файлы с таким требованием: ничего, что находится в заголовочном файле, не должно производить кода. А код производят…
• Глобальные переменные (без typedef, extern).
• Нешаблонные функции (кроме inline).
• Полностью специализированные шаблонные функции (кроме inline).
• Команда «специализировать шаблон» (template class).
При этом…
• Функции в теле struct/class автоматически inline и кода не производят.
• Для неявной специализации шаблонов существуют обходы — код генерируется дважды, но ошибки не выдаёт.
• «Свой» хедер обычно включают первым, чтобы убедиться, что в нём нет недостающих зависимостей.
В системе «одна единица компиляции» всё просто: есть ровно один файл, подлежащий компиляции. Тогда в хедерных файлах вполне могут быть конструкции, производящие код.
Системы «одна единица компиляции» и «много единиц компиляции» можно комбинировать, но надо знать:
• Все хедеры, которые производят код, должны подключаться из одной-единственной единицы компиляции. Надо чётко осознавать, из какой, и не подключать из чужих.
• У библиотеки всё равно должен быть хедер-фасад, не производящий кода и предназначенный для стыковки с другими единицами компиляции.
Такая конструкция ускоряет полную перекомпиляцию и часто применяется для библиотек, но надо знать: огромные библиотеки вроде SqLite, в 5 мегабайт препроцессированного кода, мешают распараллеливанию компиляции (ибо пока откомпилируется SqLite, остальные процессоры вполне себе соберут остальную программу).
// В схеме «одна единица компиляции»: ничего не делать.
// В схеме «много единиц компиляции»: лишняя зависимость; унести в unit1.cpp
#include <cstdlib>
// В схеме «одна единица компиляции»: убрать extern.
// В схеме «много единиц компиляции»: завести unit1.cpp, там сделать MyType x;
extern MyType X;
// В схеме «одна единица компиляции»: ничего не делать.
// В схеме «много единиц компиляции»: перенести функцию в unit1.cpp, оставив в .h только прототип.
void XReset() {}