- Организация исходного кода (шаблоны C++)
- История
- Модель включения
- Модель явного создания экземпляра
- Raymii.org
- C++ template definitions in a .cpp file (instead of a header file)
- Table of Contents
- Template definitions
- Example code
- TestClass1.h
- TestClass1.cpp
- TestClass2.h
- TestClass2.cpp
- main.cpp
- Error, undefined reference
- In the header file
Организация исходного кода (шаблоны C++)
При определении шаблона класса необходимо организовать исходный код таким образом, чтобы определения элементов были видны компилятору, когда они ему нужны. Вы можете выбрать модель включения или модель явного создания экземпляра. В модели включения определения элементов включаются в каждый файл, использующий шаблон. Это самый простой подход. Он обеспечивает максимальную гибкость с точки зрения того, какие конкретные типы могут использоваться в шаблоне. Его недостаток в том, что он может увеличивать время компиляции. Время может быть значительным, если проект или сами включенные файлы являются большими. В рамках подхода явного создания экземпляров сам шаблон создает экземпляры конкретных классов или элементов класса для определенных типов. Этот подход может уменьшить время компиляции, но он ограничивает использование только теми классами, которые разработчик шаблона включил заранее. Как правило, модель включения рекомендуется использовать, если время компиляции не является проблемой.
История
Шаблоны не похожи на обычные классы в том смысле, что компилятор не создает объектный код для шаблона или любого из его членов. Создавать нечего, пока экземпляр шаблона не будет создан с конкретными типами. Когда компилятор обнаруживает создание экземпляра шаблона, такого как MyClass mc; , и класс с такой сигнатурой еще не существует, он создает такой класс. Он также пытается создать код для любых используемых функций-элементов. Если эти определения находятся в файле, который прямо или косвенно не #included в компилируемых CPP-файлах, компилятор не сможет их увидеть. С точки зрения компилятора это не обязательно ошибка. Функции могут быть определены в другом блоке перевода, где компоновщик найдет их. Если компоновщик не находит этот код, возникает неразрешенная внешняя ошибка.
Модель включения
Самый простой и распространенный способ сделать определения шаблонов видимыми во всем блоке перевода — поместить определения в сам файл заголовка. Любой .cpp файл, использующий шаблон, просто должен иметь заголовок #include . Этот подход используется в стандартной библиотеке.
#ifndef MYARRAY #define MYARRAY #include template class MyArray < T arr[N]; public: // Full definitions: MyArray()<>void Print() < for (const auto v : arr) < std::cout > T& operator[](int i) < return arr[i]; >>; #endif
При таком подходе компилятор имеет доступ к полному определению шаблона и может создавать экземпляры шаблонов по запросу для любого типа. Он прост и относительно прост в обслуживании. Тем не менее модель включения затратнее с точки зрения времени компиляции. Эти затраты могут возрасти в крупных программах, особенно если сам заголовок шаблона содержит (#include) другие заголовки. Каждый .cpp файл, #includes заголовок, получит собственную копию шаблонов функций и всех определений. Компоновщик, как правило, сможет разобраться, чтобы не получить несколько определений для функции, но на выполнение этой работы требуется время. В случае небольших программ дополнительное время компиляции будет существенно меньше.
Модель явного создания экземпляра
Если модель включения не подходит для вашего проекта и вы точно знаете набор типов, которые будут использоваться для создания экземпляра шаблона, можно разделить код шаблона на .h файл и .cpp , а в .cpp файле явно создать экземпляр шаблонов. Этот подход создает объектный код, который будет видеть компилятор при обнаружении экземпляров пользователей.
Вы создаете явный экземпляр с помощью ключевое слово template за которым следует подпись сущности, которую вы хотите создать. Эта сущность может быть типом или членом. Если вы явно создаете экземпляр типа, будут созданы все элементы.
Файл заголовка MyArray.h объявляет класс MyArray шаблона :
//MyArray.h #ifndef MYARRAY #define MYARRAY template class MyArray < T arr[N]; public: MyArray(); void Print(); T& operator[](int i); >; #endif
Исходный файл MyArray.cpp явно создает template MyArray экземпляры и template MyArray :
//MyArray.cpp #include #include "MyArray.h" using namespace std; template MyArray::MyArray()<> template void MyArray::Print() < for (const auto v : arr) < cout cout template MyArray; template MyArray;
В предыдущем примере явные экземпляры находятся в нижней части .cpp файла. Можно MyArray использовать только для double типов или String .
В C++11 export ключевое слово не рекомендуется использовать в контексте определений шаблонов. На практике это мало что дает, потому что большинство компиляторов никогда его не поддерживали.
Raymii.org
C++ template definitions in a .cpp file (instead of a header file)
❗ This post is over four years old. It may no longer be up to date. Opinions may have changed.
Table of Contents
In this snippet I'll show you how to place your C++ template definitions in a seperate .cpp file. I'd recommend you to just put template definitions in your header file, or a .hpp file, but if you really want to there is a trick to get them in a seperate .cpp file. The trick is to explicitly instanciate every template you're going to use at the end of the .cpp file. With many different templates and types this becomes cumbersome, but for certain usecases it could be useful.
Recently I removed all Google Ads from this site due to their invasive tracking, as well as Google Analytics. Please, if you found this content useful, consider a small donation using any of the options below:
Template definitions
Small recap on templates. A template is not an actual class or function, but a "pattern" that the compiler uses to generate a family of classes or functions.
In order for the compiler to generate the code, it must see both the template definition (not just declaration) and the specific types/whatever used to "fill in" the template. For example, if you're trying to use a Foo , the compiler must see both the Foo template and the fact that you're trying to make a specific Foo . See here for more explanation.
Placing templates in your .h files might result in cluttered header files, it also could increase code bloat and the compiled binary size. (That however does depend on how smart your compiler is). For the cluttering people often resort to .hpp files. Which brings its own set of problems, for example with your build system if you're doing something special.
The trick I found here is that you can place your template definitions in a seperate .cpp file and explicitly instanciate every form of that template that is going to be used in that .cpp file.
If you don't instanciate all forms in your .cpp file you will get undefined reference errors, I'll show you an example later on.
The linker however does spew out the specific form so you can copy/paste it quickly.
Example code
I've written a sample program with a class with one template function, one other class and the main.cpp file. This is the directory layout, you can ignore the CMake files:
$ tree -L 1 . |-- CMakeLists.txt |-- TestClass1.cpp |-- TestClass1.h |-- TestClass2.cpp |-- TestClass2.h |-- cmake-build-debug `-- main.cpp 1 directory, 6 files
TestClass1.h
This file contains the class with one template function. It does not contain the template defintion, only the declaration. Normally you would define the entire template here but that's the part we don't want to in this example.
#ifndef TESTCLASS1_H #define TESTCLASS1_H #include class TestClass < private: bool m_bool1 < false >; public: TestClass(bool bool1) : m_bool1(bool1) <> // just the template declaration template void templateFunction(T1 var1, T2 var2); >; #endif //TESTCLASS1_H
TestClass1.cpp
This is where the template is defined, and at the bottom, instanciated explicitly for the types we're going to use in the code.
#include #include "TestClass1.h" //actual template definiton template void TestClass::templateFunction (T1 var1, T2 var2) < std::cout // Here is the explicit instanciation template void TestClass::templateFunction(int, int); template void TestClass::templateFunction(char const*, char const*);
TestClass2.h
This is just another class where the template is used, as an example.
#ifndef TESTCLASS2_H #define TESTCLASS2_H #include "TestClass1.h" class TestClass2 < private: bool m_abc1 ; public: void printTest(); >; #endif //TESTCLASS2_H
TestClass2.cpp
Here is the definition of the above function, where the other template is called with a const char * .
#include "TestClass2.h" void TestClass2::printTest () < TestClass example(false); example.templateFunction ("abc", "def"); >;
main.cpp
It all comes together in the main.cpp file, one of both classes. I've used two different methods of calling the class templated function, either explicitly telling which types were using or just letting the compiler figure it out.
#include #include "TestClass1.h" #include "TestClass2.h" int main () < TestClass example1(true); example1.templateFunction(1, 2); example1.templateFunction (3, 4); TestClass2 lala = TestClass2(); lala.printTest (); return 0; >
var1: 1, var2: 2, m_bool1: 1 var1: 3, var2: 4, m_bool1: 1 var1: abc, var2: def, m_bool1: 0
Error, undefined reference
The warning when you forget to instanciate a template, or in this example, uncommented one:
//template void TestClass::templateFunction(int, int); template void TestClass::templateFunction(char const*, char const*);
[100%] Linking CXX executable example CMakeFiles/folder.dir/main.cpp.o: In function `main': folder/main.cpp:7: undefined reference to `void TestClass::templateFunction(int, int)' folder/main.cpp:8: undefined reference to `void TestClass::templateFunction(int, int)' collect2: error: ld returned 1 exit status
If you would use the template with two doubles you would have to add this at the end of the file TestClass1.cpp :
template void TestClass::templateFunction(double, double);
In the header file
If the template function for TestClass1 was in the header file, it would look like this:
#ifndef TESTCLASS1_H #define TESTCLASS1_H #include class TestClass < private: bool m_bool1 < false >; public: TestClass(bool bool1) : m_bool1(bool1) <> // template declaration and definiton template void templateFunction (T1 var1, T2 var2) < std::cout >; #endif //TESTCLASS1_H
You would not need the TestClass1.cpp file.