Использование внешнего шаблона (C++11)
Рисунок 1: шаблоны функций
TemplHeader.h
template<typename T>
void f();
TemplCpp.cpp
template<typename T>
void f(){
//...
}
//explicit instantation
template void f<T>();
main.cpp
#include "TemplHeader.h"
extern template void f<T>(); //is this correct?
int main() {
f<char>();
return 0;
}
Это правильный способ использования extern template
или я использую это ключевое слово только для шаблонов классов, как на рисунке 2?
Рисунок 2: шаблоны классов
TemplHeader.h
template<typename T>
class foo {
T f();
};
TemplCpp.cpp
template<typename T>
void foo<T>::f() {
//...
}
//explicit instantation
template class foo<int>;
main.cpp
#include "TemplHeader.h"
extern template class foo<int>();
int main() {
foo<int> test;
return 0;
}
Я знаю, что все это нужно поместить в один заголовочный файл, но если мы создадим шаблоны с одинаковыми параметрами в нескольких файлах, то получим несколько одинаковых определений, и компилятор удалит их все (кроме одного), чтобы избежать ошибок. Как я использую extern template
? Можем ли мы использовать его только для классов или же для функций?
Кроме того, рисунки 1 и 2 могут быть расширены до решения, в котором шаблоны находятся в одном заголовочном файле. В этом случае нам нужно использовать extern template
Ключевое слово, чтобы избежать нескольких одинаковых экземпляров. Это только для классов или функций тоже?
4 ответа
Вы должны использовать только extern template
заставить компилятор не создавать экземпляр шаблона, если вы знаете, что он будет создан где-то еще. Он используется для уменьшения времени компиляции и размера объектного файла.
Например:
// header.h
template<typename T>
void ReallyBigFunction()
{
// Body
}
// source1.cpp
#include "header.h"
void something1()
{
ReallyBigFunction<int>();
}
// source2.cpp
#include "header.h"
void something2()
{
ReallyBigFunction<int>();
}
Это приведет к созданию следующих объектных файлов:
source1.o
void something1()
void ReallyBigFunction<int>() // Compiled first time
source2.o
void something2()
void ReallyBigFunction<int>() // Compiled second time
Если оба файла связаны друг с другом, один void ReallyBigFunction<int>()
будет отброшен, что приведет к потере времени компиляции и размера объектного файла.
Чтобы не тратить время на компиляцию и размер файла объекта, существует extern
ключевое слово, которое заставляет компилятор не компилировать функцию шаблона. Вы должны использовать это, если и только если вы знаете, что он используется в том же двоичном файле где-то еще.
изменения source2.cpp
чтобы:
// source2.cpp
#include "header.h"
extern template void ReallyBigFunction<int>();
void something2()
{
ReallyBigFunction<int>();
}
Результатом будут следующие объектные файлы:
source1.o
void something1()
void ReallyBigFunction<int>() // compiled just one time
source2.o
void something2()
// No ReallyBigFunction<int> here because of the extern
Когда оба они будут связаны друг с другом, второй объектный файл будет просто использовать символ из первого объектного файла. Нет необходимости отбрасывать и не тратить время на компиляцию и размер файла объекта.
Это следует использовать только внутри проекта, например, когда вы используете vector<int>
несколько раз, вы должны использовать extern
во всех, кроме одного исходного файла.
Это также относится к классам и функциям как единым целым, и даже к функциям-членам шаблона.
Википедия имеет лучшее описание
В C++03 компилятор должен создавать экземпляр шаблона всякий раз, когда в модуле перевода встречается полностью указанный шаблон. Если шаблон создается с одними и теми же типами во многих единицах перевода, это может значительно увеличить время компиляции. В C++03 нет способа предотвратить это, поэтому в C++ 11 введены объявления шаблонов extern, аналогичные объявлениям extern данных.
C++03 имеет такой синтаксис, чтобы обязать компилятор создавать экземпляр шаблона:
template class std::vector<MyClass>;
C++ 11 теперь обеспечивает этот синтаксис:
extern template class std::vector<MyClass>;
который говорит компилятору не создавать экземпляр шаблона в этом модуле перевода.
Предупреждение:nonstandard extension used...
В Microsoft VC++ уже несколько лет используется нестандартная версия этой функции (на C++03). Об этом предупреждает компилятор, чтобы избежать проблем переносимости с кодом, который также необходимо компилировать на разных компиляторах.
Посмотрите на образец на связанной странице, чтобы увидеть, что он работает примерно так же. Вы можете ожидать, что сообщение исчезнет с будущими версиями MSVC, за исключением, конечно, одновременного использования других нестандартных расширений компилятора.
extern template
требуется только в том случае, если объявление шаблона завершено
На это намекали в других ответах, но я не думаю, что этому уделялось достаточно внимания.
Это означает, что в примерах OP extern template
не имеет никакого эффекта, потому что определения шаблона в заголовках были неполными:
void f();
: просто декларация, без телаclass foo
: объявляет методf()
но не имеет определения
Поэтому я бы рекомендовал просто удалить extern template
определение в этом конкретном случае: вам нужно только добавить их, если классы полностью определены.
Например:
TemplHeader.h
template<typename T>
void f();
TemplCpp.cpp
template<typename T>
void f(){}
// Explicit instantiation for char.
template void f<char>();
Main.cpp
#include "TemplHeader.h"
// Commented out from OP code, has no effect.
// extern template void f<T>(); //is this correct?
int main() {
f<char>();
return 0;
}
компилировать и просматривать символы с помощью nm
:
g++ -std=c++11 -Wall -Wextra -pedantic -c -o TemplCpp.o TemplCpp.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -c -o Main.o Main.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -o Main.out Main.o TemplCpp.o
echo TemplCpp.o
nm -C TemplCpp.o | grep f
echo Main.o
nm -C Main.o | grep f
выход:
TemplCpp.o
0000000000000000 W void f<char>()
Main.o
U void f<char>()
а затем из man nm
Мы видим, что U
означает undefined, поэтому определение осталось только на TemplCpp
по желанию.
Все это сводится к компромиссу между полными объявлениями заголовков:
- плюсы:
- позволяет внешнему коду использовать наш шаблон с новыми типами
- у нас есть возможность не добавлять явные экземпляры, если нас устраивает раздувание объекта
- минусы:
- при разработке этого класса изменения реализации заголовка приведут к тому, что интеллектуальные системы сборки перестроят все включающие, что может быть много-много файлов
- если мы хотим избежать раздувания объектного файла, нам нужно не только делать явные экземпляры (как и с неполными объявлениями заголовков), но также добавлять
extern template
на каждом включателе, что программисты, вероятно, забудут сделать
Дальнейшие их примеры показаны по адресу: Явное создание шаблона - когда оно используется?
Поскольку время компиляции очень важно в больших проектах, я настоятельно рекомендую использовать неполные объявления шаблонов, если только сторонним сторонам не потребуется повторно использовать ваш код со своими собственными сложными пользовательскими классами.
И в этом случае я бы сначала попытался использовать полиморфизм, чтобы избежать проблемы времени сборки, и использовать шаблоны только в том случае, если можно добиться заметного прироста производительности.
Протестировано в Ubuntu 18.04.
Известной проблемой с шаблонами является раздувание кода, которое является следствием генерации определения класса в каждом модуле, который вызывает специализацию шаблона класса. Чтобы предотвратить это, начиная с C++0x, можно использовать ключевое слово extern перед специализацией шаблона класса.
#include <MyClass>
extern class CMyClass<int>;
Явное создание экземпляра класса шаблона должно происходить только в одном модуле перевода, предпочтительно с определением шаблона (MyClass.cpp).
template class CMyClass<int>;
template class CMyClass<float>;
Если вы ранее использовали extern для функций, то же самое относится и к шаблонам. в противном случае может помочь выход extern для простых функций. Кроме того, вы можете поместить extern(ы) в заголовочный файл и включать заголовок, когда вам это нужно.