Это связано с требованием отдельной компиляции и тем, что шаблоны являются полиморфизмом в стиле экземпляров.
Давайте немного приблизимся к конкретному для объяснения. Скажем, у меня есть следующие файлы:
- foo.h
- объявляет интерфейс
class MyClass<T>
- foo.cpp
- определяет реализацию
class MyClass<T>
- bar.cpp
Отдельная компиляция означает, что я должен иметь возможность компилировать foo.cpp независимо от bar.cpp . Компилятор выполняет всю тяжелую работу по анализу, оптимизации и генерации кода на каждом модуле компиляции полностью независимо; нам не нужно делать анализ всей программы. Только компоновщик должен обрабатывать всю программу одновременно, и работа компоновщика существенно проще.
bar.cpp даже не нужно существовать, когда я компилирую foo.cpp , но я все равно должен иметь возможность связать foo.o Я уже имел вместе с bar.o , который я только что произвел, без необходимости перекомпилировать foo.cpp . foo.cpp можно даже скомпилировать в динамическую библиотеку, распространять куда-либо еще без foo.cpp и связывать с кодом, который они пишут спустя годы после того, как я написал foo.cpp .
«Полиморфизм стиля реализации» означает, что шаблон MyClass<T>
на самом деле не является универсальным классом, который можно скомпилировать в код, который может работать для любого значения T
. Это добавило бы дополнительные издержки, такие как упаковка, необходимость передавать указатели на функции для распределителей и конструкторов и т. Д. Цель шаблонов C ++ состоит в том, чтобы избежать необходимости писать почти идентичные class MyClass_int
, class MyClass_float
и т. Д., Но при этом иметь возможность завершать работу с скомпилированным кодом, который, как правило, выглядит так, как будто мы написали каждую версию отдельно Таким образом, шаблон буквально шаблон; шаблон класса не класс, это рецепт для создания нового класса для каждого T
, с которым мы сталкиваемся. Шаблон не может быть скомпилирован в код, может быть скомпилирован только результат создания шаблона.
Поэтому, когда foo.cpp скомпилирован, компилятор не может увидеть bar.cpp , чтобы узнать, что необходим MyClass<int>
. Он может видеть шаблон MyClass<T>
, но не может генерировать код для этого (это шаблон, а не класс). И когда bar.cpp компилируется, компилятор может видеть, что ему нужно создать MyClass<int>
, но он не может видеть шаблон MyClass<T>
(только его интерфейс в foo.h). ), поэтому он не может его создать.
Если foo.cpp сам использует MyClass<int>
, то код для этого будет сгенерирован при компиляции foo.cpp , поэтому, когда bar.o связанные с foo.o они могут быть подключены и будут работать. Мы можем использовать этот факт, чтобы разрешить реализацию конечного набора шаблонов в файле .cpp, написав один шаблон. Но bar.cpp не может использовать шаблон в качестве шаблона и создавать его экземпляры для любых типов; он может использовать только существующие версии шаблонного класса, которые, как предполагал автор foo.cpp .
Вы можете подумать, что при компиляции шаблона компилятор должен «генерировать все версии», а те, которые никогда не используются, отфильтровываются во время компоновки. Помимо огромных накладных расходов и чрезвычайных трудностей, с которыми столкнулся бы такой подход, потому что функции «модификатора типа», такие как указатели и массивы, позволяют даже только встроенным типам создавать бесконечное число типов, что происходит, когда я теперь расширяю свою программу добавив:
- baz.cpp
- объявляет и реализует
class BazPrivate
и использует MyClass<BazPrivate>
Невозможно, чтобы это работало, если только мы не
- Приходится перекомпилировать foo.cpp каждый раз, когда мы изменяем любой другой файл в программе , в случае, если добавлен новый экземпляр экземпляра
MyClass<T>
- Требуется, чтобы baz.cpp содержал (возможно, через заголовок) полный шаблон
MyClass<T>
, чтобы компилятор мог генерировать MyClass<BazPrivate>
во время компиляции baz.cpp .
Никому не нравится (1), потому что системам компиляции анализа всей программы требуется навсегда для компиляции, и потому что это делает невозможным распространение скомпилированных библиотек без исходного кода. Таким образом, вместо этого мы имеем (2).