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


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

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

Почему это?

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


1356


источник


Ответы:


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

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

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 по этой теме ,


1177



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

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

Изменить: добавление примера явного создания экземпляра шаблона. Используется после определения шаблона, и определены все функции-члены.

template class vector<int>;

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

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


199



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

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

  • 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).


170



Templates need to be instantiated by the compiler before actually compiling them into object code. This instantiation can only be achieved if the template arguments are known. Now imagine a scenario where a template function is declared in a.h, defined in a.cpp and used in b.cpp. When a.cpp is compiled, it is not necessarily known that the upcoming compilation b.cpp will require an instance of the template, let alone which specific instance would that be. For more header and source files, the situation can quickly get more complicated.

One can argue that compilers can be made smarter to "look ahead" for all uses of the template, but I'm sure that it wouldn't be difficult to create recursive or otherwise complicated scenarios. AFAIK, compilers don't do such look aheads. As Anton pointed out, some compilers support explicit export declarations of template instantiations, but not all compilers support it (yet?).


63



Actually, versions of the C++ standard before C++11 defined the 'export' keyword, that would make it possible to simply declare templates in a header file and implement them elsewhere.

Unfortunately, none of the popular compilers implemented this keyword. The only one I know about is the frontend written by the Edison Design Group, which is used by the Comeau C++ compiler. All others insisted you write templates in header files, needing the definition of the code for proper instantiation (as others pointed out already).

As a result, the ISO C++ standard committee decided to remove the export feature of templates beginning with C++11.


46



Although standard C++ has no such requirement, some compilers require that all function and class templates need to be made available in every translation unit they are used. In effect, for those compilers, the bodies of template functions must be made available in a header file. To repeat: that means those compilers won't allow them to be defined in non-header files such as .cpp files

There is an export keyword which is supposed to mitigate this problem, but it's nowhere close to being portable.


29



Templates must be used in headers because the compiler needs to instantiate different versions of the code, depending on the parameters given/deduced for template parameters. Remember that a template doesn't represent code directly, but a template for several versions of that code. When you compile a non-template function in a .cpp file, you are compiling a concrete function/class. This is not the case for templates, which can be instantiated with different types, namely, concrete code must be emitted when replacing template parameters with concrete types.

There was a feature with the export keyword that was meant to be used for separate compilation. The export feature is deprecated in C++11 and, AFAIK, only one compiler implemented it. You shouldn't make use of export. Separate compilation is not possible in C++ or C++11 but maybe in C++17, if concepts make it in, we could have some way of separate compilation.

For separate compilation to be achieved, separate template body checking must be possible. It seems that a solution is possible with concepts. Take a look at this paper recently presented at the standards commitee meeting. I think this is not the only requirement, since you still need to instantiate code for the template code in user code.

The separate compilation problem for templates I guess it's also a problem that is arising with the migration to modules, which is currently being worked.


26



It means that the most portable way to define method implementations of template classes is to define them inside the template class definition.

template < typename ... >
class MyClass
{

    int myMethod()
    {
       // Not just declaration. Add method implementation here
    }
};

13



Even though there are plenty of good explanations above, I'm missing a practical way to separate templates into header and body.
My main concern is avoiding recompilation of all template users, when I change its definition.
Having all template instantiations in the template body is not a viable solution for me, since the template author may not know all if its usage and the template user may not have the right to modify it.
I took the following approach, which works also for older compilers (gcc 4.3.4, aCC A.03.13).

For each template usage there's a typedef in its own header file (generated from the UML model). Its body contains the instantiation (which ends up in a library which is linked in at the end).
Each user of the template includes that header file and uses the typedef.

A schematic example:

MyTemplate.h:

#ifndef MyTemplate_h
#define MyTemplate_h 1

template <class T>
class MyTemplate
{
public:
  MyTemplate(const T& rt);
  void dump();
  T t;
};

#endif

MyTemplate.cpp:

#include "MyTemplate.h"
#include <iostream>

template <class T>
MyTemplate<T>::MyTemplate(const T& rt)
: t(rt)
{
}

template <class T>
void MyTemplate<T>::dump()
{
  cerr << t << endl;
}

MyInstantiatedTemplate.h:

#ifndef MyInstantiatedTemplate_h
#define MyInstantiatedTemplate_h 1
#include "MyTemplate.h"

typedef MyTemplate< int > MyInstantiatedTemplate;

#endif

MyInstantiatedTemplate.cpp:

#include "MyTemplate.cpp"

template class MyTemplate< int >;

main.cpp:

#include "MyInstantiatedTemplate.h"

int main()
{
  MyInstantiatedTemplate m(100);
  m.dump();
  return 0;
}

This way only the template instantiations will need to be recompiled, not all template users (and dependencies).


9



That is exactly correct because the compiler has to know what type it is for allocation. So template classes, functions, enums,etc.. must be implemented as well in the header file if it is to be made public or part of a library (static or dynamic) because header files are NOT compiled unlike the c/cpp files which are. If the compiler doesn't know the type is can't compile it. In .Net it can because all objects derive from the Object class. This is not .Net.


6