Почему шаблоны могут быть реализованы только в заголовочном файле?

Цитата из Стандартная библиотека C ++: учебное пособие и справочник:

Единственный переносимый способ использования шаблонов на данный момент — это реализовать их в заголовочных файлах с помощью встроенных функций.

Почему это?

(Пояснение: заголовочные файлы не являются только портативное решение. Но они являются наиболее удобным портативным решением.)

1491

Решение

это не Необходимо поместить реализацию в заголовочный файл, см. альтернативное решение в конце этого ответа.

В любом случае, причина вашего кода заключается в том, что при создании экземпляра шаблона компилятор создает новый класс с заданным аргументом шаблона. Например:

template<typename T>
struct Foo
{
T bar;
void doSomething(T param) {/* do stuff using T */}
};

// somewhere in a .cpp
Foo<int> f;

При чтении этой строки компилятор создаст новый класс (назовем его FooInt), что эквивалентно следующему:

struct FooInt
{
int bar;
void doSomething(int param) {/* do stuff using int */}
}

Следовательно, компилятор должен иметь доступ к реализации методов, чтобы создавать их экземпляры с помощью аргумента шаблона (в этом случае int). Если бы эти реализации не были в заголовке, они не были бы доступны, и поэтому компилятор не смог бы создать экземпляр шаблона.

Распространенным решением этой проблемы является запись объявления шаблона в файл заголовка, затем реализация класса в файле реализации (например, .tpp) и включение этого файла реализации в конец заголовка.

// Foo.h
template <typename T>
struct Foo
{
void doSomething(T param);
};

#include "Foo.tpp"
// Foo.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
//implementation
}

Таким образом, реализация по-прежнему отделена от объявления, но доступна для компилятора.

Другое решение состоит в том, чтобы отделить реализацию и явно создать экземпляр всех необходимых вам шаблонов:

// Foo.h

// no implementation
template <typename T> struct Foo { ... };

//----------------------------------------
// Foo.cpp

// implementation of Foo's methods

// explicit instantiations
template class Foo<int>;
template class Foo<float>;
// You will only be able to use Foo with int or float

Если мое объяснение недостаточно ясно, вы можете посмотреть на C ++ Super-FAQ по этому вопросу.

1294

Другие решения

Здесь много правильных ответов, но я хотел бы добавить это (для полноты):

Если вы, в нижней части файла cpp реализации, сделаете явное создание экземпляров всех типов, с которыми будет использоваться шаблон, компоновщик сможет найти их как обычно.

Редактировать: Добавление примера явной реализации шаблона. Используется после определения шаблона и определения всех функций-членов.

template class vector<int>;

Это создаст экземпляр (и, следовательно, сделает доступным для компоновщика) класс и все его функции-члены (только). Аналогичный синтаксис работает для шаблонных функций, поэтому, если у вас есть перегрузки операторов, не являющихся членами, вам может потребоваться сделать то же самое для них.

Приведенный выше пример довольно бесполезен, поскольку вектор полностью определен в заголовках, за исключением случаев, когда используется общий включаемый файл (предварительно скомпилированный заголовок?) extern template class vector<int> чтобы не создавать его во всех Другой (1000?) Файлы, которые используют вектор.

214

Это связано с требованием отдельной компиляции и тем, что шаблоны являются полиморфизмом в стиле экземпляров.

Давайте немного приблизимся к конкретному для объяснения. Скажем, у меня есть следующие файлы:

  • foo.h
    • объявляет интерфейс class MyClass<T>
  • foo.cpp
    • определяет реализацию class MyClass<T>
  • bar.cpp
    • использования MyClass<int>

Отдельная компиляция означает, что я должен быть в состоянии собрать 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>

Нет никакого способа, которым это могло бы работать, если мы или

  1. Придется перекомпилировать foo.cpp каждый раз, когда мы меняемся любой другой файл в программе, в случае, если он добавил новый роман создания MyClass<T>
  2. Требуют, чтобы baz.cpp содержит (возможно, через заголовок включает) полный шаблон MyClass<T>, так что компилятор может генерировать MyClass<BazPrivate> во время компиляции baz.cpp.

Никто не любит (1), потому что системы компиляции анализа всей программы занимают навсегда компилировать, и потому что это делает невозможным распространение скомпилированных библиотек без исходного кода. Таким образом, мы имеем (2) вместо этого.

191

Шаблоны должны быть инстанцирован компилятором, прежде чем фактически скомпилировать их в объектный код. Эта реализация может быть достигнута только в том случае, если известны аргументы шаблона. Теперь представьте сценарий, в котором функция шаблона объявлена ​​в a.h, определенный в a.cpp и используется в b.cpp, когда a.cpp компилируется, не обязательно известно, что предстоящая компиляция b.cpp потребует экземпляр шаблона, не говоря уже о том, какой конкретный экземпляр будет. Для большего количества заголовочных и исходных файлов ситуация может быстро усложниться.

Можно утверждать, что компиляторы могут быть умнее, чтобы «смотреть в будущее» для всех применений шаблона, но я уверен, что не будет трудно создавать рекурсивные или иные сложные сценарии. AFAIK, компиляторы не делают такой взгляд вперед. Как указал Антон, некоторые компиляторы поддерживают явные объявления экспорта экземпляров шаблона, но не все компиляторы поддерживают его (пока?).

68

На самом деле, до C ++ 11 стандарт определял export Ключевое слово, которое было бы сделать возможным объявить шаблоны в заголовочном файле и внедрить их в другом месте.

Ни один из популярных компиляторов не реализовал это ключевое слово. Единственный, о котором я знаю, — это интерфейс, написанный Edison Design Group, который используется компилятором Comeau C ++. Все остальные требовали, чтобы вы писали шаблоны в заголовочных файлах, потому что компилятору нужно определение шаблона для правильной реализации (как уже указывали другие).

В результате комитет по стандарту ISO C ++ решил удалить export особенность шаблонов с C ++ 11.

55

Хотя в стандарте C ++ такого требования нет, некоторые компиляторы требуют, чтобы все шаблоны функций и классов были доступны в каждом используемом модуле перевода. По сути, для этих компиляторов тела шаблонных функций должны быть доступны в заголовочном файле. Повторим: это означает, что эти компиляторы не позволят им быть определены в файлах без заголовка, таких как файлы .cpp

Есть экспорт ключевое слово, которое должно смягчить эту проблему, но оно далеко не портативное.

32

Шаблоны должны использоваться в заголовках, потому что компилятор должен создавать различные версии кода в зависимости от параметров, заданных / выведенных для параметров шаблона. Помните, что шаблон не представляет код напрямую, а шаблон для нескольких версий этого кода.
Когда вы компилируете не шаблонную функцию в .cpp файл, вы компилируете конкретную функцию / класс. Это не относится к шаблонам, которые могут быть созданы с различными типами, а именно, конкретный код должен генерироваться при замене параметров шаблона на конкретные типы.

Была особенность с export Ключевое слово, которое должно было использоваться для отдельной компиляции.
export функция устарела в C++11 и, AFAIK, только один компилятор реализовал это. Вы не должны использовать export, Отдельная компиляция невозможна в C++ или же C++11 но возможно в C++17, если концепции делают это, у нас мог бы быть некоторый способ отдельной компиляции.

Для достижения отдельной компиляции должна быть возможна отдельная проверка тела шаблона. Кажется, что решение возможно с концепциями. Взгляните на это бумага недавно представлен на
заседание комитета по стандартам. Я думаю, что это не единственное требование, так как вам все еще нужно создать экземпляр кода для шаблона в пользовательском коде.

Отдельная проблема компиляции для шаблонов, я думаю, это также проблема, возникающая при переходе на модули, которая в настоящее время работает.

26

Это означает, что наиболее переносимым способом определения реализаций методов шаблонных классов является определение их внутри определения класса шаблона.

template < typename ... >
class MyClass
{

int myMethod()
{
// Not just declaration. Add method implementation here
}
};
13
По вопросам рекламы [email protected]